/******************************************************************************
*
* Copyright (C) 2019-2020  Brian Berlin
* 
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* 
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* 
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************
*
* FILENAME: test_args.c
*
* DESCRIPTION:  This contains tests for command line arguments
*
* AUTHORS:  bberlin
*
* CHANGE LOG
*
*  VER     DATE     CHANGED BY   DESCRIPTION OF CHANGE
* ------  --------  ----------   ------------------------------------
* 0.9.0   05/19/20  bberlin      Creation
******************************************************************************/
#include <SDL3/SDL.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include "../src/interface/katconfig.h"
#include "../src/interface/util.h"
#include "../src/interface/media_if.h"
#include "../src/interface/logger.h"
#include "../src/interface/input.h"
#include "../src/interface/sdl_if.h"
#include "../src/interface/toml.h"

static int failure = 0;
extern t_config config;

void output_result ( int result, const char *test, const char *value, 
        const char *error ) {

    if ( result ) {
        printf ( "\e[32m[PASS]\e[m %s Result=%s\n", test, value );
    }
    else {
        failure++;
        printf ( "\e[31m[FAIL]\e[m %s %s Result=%s\n", test, error, value );
    }

}

int check_int ( const char * test, int value, int answer ) {

    char value_string[65];
    char error[257];

    sprintf ( value_string, "%d", value );

    if ( value == answer ) {
        output_result ( 1, test, value_string, "" );
    }
    else {
        sprintf ( error, "Value not %d", answer );
        output_result ( 0, test, value_string, error );
    }
}

int check_float ( const char * test, float value, float answer ) {

    char value_string[65];
    char error[257];

    sprintf ( value_string, "%0.2f", value );

    if ( value == answer ) {
        output_result ( 1, test, value_string, "" );
    }
    else {
        sprintf ( error, "Value not %0.2f", answer );
        output_result ( 0, test, value_string, error );
    }
}

int check_string ( const char * test, char *value, char *answer ) {

    char error[257];

    if ( !strcmp(value, answer) ) {
        output_result ( 1, test, value, "" );
    }
    else {
        sprintf ( error, "Value not %s", answer );
        output_result ( 0, test, value, error );
    }
}

int check_string_null ( const char * test, char *value, char *answer ) {

    char error[257];

    if ( (value == NULL && answer == NULL) ||
            (value && answer && !strcmp(value, answer)) ) {
        output_result ( 1, test, value ? value : "NULL", "" );
    }
    else {
        sprintf ( error, "Value not %s", answer ? answer : "NULL" );
        output_result ( 0, test, value ? value : "NULL", error );
    }
    return 0;
}

/* Run the launch-time argument parser over a crafted argv and verify the
   recovery options (--shader / --backend / --safe) land as documented,
   including order independence. */
extern char **g_argv;
extern int   g_argc;

static void run_recovery_parse ( char **args, int n ) {
    g_argc = n;
    g_argv = args;
    config_check_arguments ();
}

void check_recovery_options ( void ) {

    char *a1[] = { "kat5200", "--shader", "none" };
    char *a2[] = { "kat5200", "--backend", "default" };
    char *a3[] = { "kat5200", "--backend", "opengl", "--safe" };
    char *a4[] = { "kat5200", "--safe", "--backend", "gpu" };
    char *a5[] = { "kat5200", "--safe", "--shader", "crt" };

    /* --shader none disables shader */
    util_alloc_string ( &config.video.shader, "something" );
    run_recovery_parse ( a1, 3 );
    check_string ( "Recovery.ShaderNone", config.video.shader, "None" );

    /* --backend default clears the renderer name */
    util_alloc_string ( &config.video.renderer_name, "opengl" );
    run_recovery_parse ( a2, 3 );
    check_string_null ( "Recovery.BackendDefault", config.video.renderer_name, NULL );

    /* Explicit --backend wins over --safe regardless of order */
    util_alloc_string ( &config.video.renderer_name, "stale" );
    run_recovery_parse ( a3, 4 );
    check_string ( "Recovery.BackendThenSafe", config.video.renderer_name, "opengl" );
    check_int ( "Recovery.SafeFlagSet", config_get_safe_video_mode(), 1 );

    run_recovery_parse ( a4, 4 );
    check_string ( "Recovery.SafeThenBackend", config.video.renderer_name, "gpu" );

    /* Explicit --shader wins over --safe; renderer stays at safe default */
    run_recovery_parse ( a5, 4 );
    check_string ( "Recovery.SafeThenShader", config.video.shader, "crt" );
    check_string_null ( "Recovery.SafeRendererNull", config.video.renderer_name, NULL );

    /* --start_with must be applied during config_init, before the first
       run_system launch decision reads config.general.start_with. */
    {
        char *a_start[] = { "kat5200", "--start_with", "file_browser" };
        config_init ( 3, a_start );
        check_int ( "Startup.StartWithFileBrowser",
                config.general.start_with, START_GUI_BROWSER );
    }

    /* --safe takes no operand: a following ROM token must still be honored.
       Goes through config_init so the early (pre-GUI) pass is exercised. */
    {
        char *a6[] = { "kat5200", "--safe", "game.rom" };
        config_init ( 3, a6 );
        check_int ( "Recovery.SafeKeepsRom",
                config.general.rom &&
                strstr(config.general.rom, "game.rom") ? 1 : 0, 1 );
        check_string ( "Recovery.SafeRomShaderNone", config.video.shader, "None" );
    }
}

void check_sound_buffer_save_load ( void ) {

    t_toml_node *sound_tbl;
    char *saved_value;

    config.sound.buffer_size = 2.5f;
    config_save_sound ( config_get_global_table(), "All", KCFG_SND_BUFFER );
    sound_tbl = toml_get_table_array_by_key (
            config_get_global_table()->child, "sound", "tag", "All" );
    saved_value = sound_tbl ? toml_get_key_value(sound_tbl, "buffer_size") : NULL;
    check_string_null ( "Sound Buffer Save Float", saved_value, "2.5000" );

    config.sound.buffer_size = KCFG_SND_BUFFER_AUTO;
    config_load_sound ( config_get_global_table(), "All" );
    check_float ( "Sound Buffer Reload Float", config.sound.buffer_size, 2.5f );

    config.sound.buffer_size = KCFG_SND_BUFFER_AUTO;
    config_save_sound ( config_get_global_table(), "All", KCFG_SND_BUFFER );
    sound_tbl = toml_get_table_array_by_key (
            config_get_global_table()->child, "sound", "tag", "All" );
    saved_value = sound_tbl ? toml_get_key_value(sound_tbl, "buffer_size") : NULL;
    check_string_null ( "Sound Buffer Save Auto", saved_value, "0.0000" );

    config.sound.buffer_size = KCFG_SND_BUFFER_DEFAULT;
    config_load_sound ( config_get_global_table(), "All" );
    check_float ( "Sound Buffer Reload Auto", config.sound.buffer_size,
            KCFG_SND_BUFFER_AUTO );
}

int check_input ( const char * test, t_input_common *value, t_input_common *answer ) {

    char error[257] = "";

    if ( value->enable != answer->enable )
        sprintf ( error, "Enable not equal" );
    else if ( value->device[0] != answer->device[0] ) 
        sprintf ( error, "Device not equal" );
    else if ( value->device_num[0] != answer->device_num[0] ) 
        sprintf ( error, "Device num not equal" );
    else if ( value->part_type[0] != answer->part_type[0] ) 
        sprintf ( error, "Part type not equal" );
    else if ( value->part_num[0] != answer->part_num[0] ) 
        sprintf ( error, "Part num not equal" );
    else if ( value->direction[0] != answer->direction[0] ) 
        sprintf ( error, "Direction not equal" );
    else if ( value->combo != answer->combo ) 
        sprintf ( error, "Combo not equal" );
    else if ( answer->combo ) {
        if ( value->device[1] != answer->device[1] ) 
            sprintf ( error, "Device 1 not equal" );
        else if ( value->device_num[1] != answer->device_num[1] ) 
            sprintf ( error, "Device num 1 not equal" );
        else if ( value->part_type[1] != answer->part_type[1] ) 
            sprintf ( error, "Part type 1 not equal" );
        else if ( value->part_num[1] != answer->part_num[1] ) 
            sprintf ( error, "Part num 1 not equal" );
        else if ( value->direction[1] != answer->direction[1] ) 
            sprintf ( error, "Direction 1 not equal" );
    }

    if ( strlen(error) )
        output_result ( 0, test, "", error );
    else
        output_result ( 1, test, "", "" );
}

/******************************************************************************
**  Function   :  main
**                                                
**  Objective  :  This is the main used to test the media module
**                                               
**  Parameters :  NONE                          
**                                             
**  return     :  0 if success                
**                -1 if failure               
******************************************************************************/ 
int main ( int argc, char *argv[]  ) {

    int i;
    int t_argc = -1;
    char t_argv[240][200];
    char *p_argv[240];
    char mapping_input[20];
    t_input_common input_answer;

    for (int i = 0; i < 240; ++i ) {
        p_argv[i] = t_argv[i];
    }

    /* 
     * Create the argv array with command line args
     */
    strcpy ( t_argv[++t_argc], "kat5200" );
    strcpy ( t_argv[++t_argc], "--state" );
    strcpy ( t_argv[++t_argc], "test.zip" );
    strcpy ( t_argv[++t_argc], "--quick_state" );
    strcpy ( t_argv[++t_argc], "8" );
    strcpy ( t_argv[++t_argc], "--start_with" );
    strcpy ( t_argv[++t_argc], "file_browser" );
    strcpy ( t_argv[++t_argc], "--throttle" );
    strcpy ( t_argv[++t_argc], "78" );
    strcpy ( t_argv[++t_argc], "--sio_patch" );
    strcpy ( t_argv[++t_argc], "false" );
    strcpy ( t_argv[++t_argc], "--general.basic_enable" );
    strcpy ( t_argv[++t_argc], "true" );
    strcpy ( t_argv[++t_argc], "--general.machine" );
    strcpy ( t_argv[++t_argc], "xe" );
    strcpy ( t_argv[++t_argc], "--mapping" );
    strcpy ( t_argv[++t_argc], "5200.16K_ONE_CHIP" );
    strcpy ( t_argv[++t_argc], "--machine.tv_standard" );
    strcpy ( t_argv[++t_argc], "PAL" );
    strcpy ( t_argv[++t_argc], "--machine.ram" );
    strcpy ( t_argv[++t_argc], "64" );
    strcpy ( t_argv[++t_argc], "--machine.bios" );
    strcpy ( t_argv[++t_argc], "bios.zip/bios.rom" );
    strcpy ( t_argv[++t_argc], "--machine.profiles_dir" );
    strcpy ( t_argv[++t_argc], "C:\\Users\\Test\\Profs" );
    strcpy ( t_argv[++t_argc], "--video.fullscreen" );
    strcpy ( t_argv[++t_argc], "1" );
    strcpy ( t_argv[++t_argc], "--video.fixed_window" );
    strcpy ( t_argv[++t_argc], "true" );
    strcpy ( t_argv[++t_argc], "--video.width" );
    strcpy ( t_argv[++t_argc], "789" );
    strcpy ( t_argv[++t_argc], "--video.height" );
    strcpy ( t_argv[++t_argc], "567" );
    strcpy ( t_argv[++t_argc], "--video.x" );
    strcpy ( t_argv[++t_argc], "56" );
    strcpy ( t_argv[++t_argc], "--video.y" );
    strcpy ( t_argv[++t_argc], "109" );
    strcpy ( t_argv[++t_argc], "--video.wide_playfield" );
    strcpy ( t_argv[++t_argc], "true" );
    strcpy ( t_argv[++t_argc], "--video.gui_scale" );
    strcpy ( t_argv[++t_argc], "150" );
    strcpy ( t_argv[++t_argc], "--video.window_scaling" );
    strcpy ( t_argv[++t_argc], "fit_window" );
    strcpy ( t_argv[++t_argc], "--video.texture_scaling" );
    strcpy ( t_argv[++t_argc], "smooth" );
    strcpy ( t_argv[++t_argc], "--video.palette" );
    strcpy ( t_argv[++t_argc], "bright" );
    strcpy ( t_argv[++t_argc], "--video.shader" );
    strcpy ( t_argv[++t_argc], "test_shader" );
    strcpy ( t_argv[++t_argc], "--backend" );
    strcpy ( t_argv[++t_argc], "opengl" );
    strcpy ( t_argv[++t_argc], "--video.vsync" );
    strcpy ( t_argv[++t_argc], "0" );
    strcpy ( t_argv[++t_argc], "--video.ntsc_artifacts" );
    strcpy ( t_argv[++t_argc], "true" );
    strcpy ( t_argv[++t_argc], "--video.ntsc_setup.hue" );
    strcpy ( t_argv[++t_argc], "0.1" );
    strcpy ( t_argv[++t_argc], "--video.ntsc_setup.saturation" );
    strcpy ( t_argv[++t_argc], "0.5" );
    strcpy ( t_argv[++t_argc], "--video.ntsc_setup.contrast" );
    strcpy ( t_argv[++t_argc], "-0.6" );
    strcpy ( t_argv[++t_argc], "--video.ntsc_setup.brightness" );
    strcpy ( t_argv[++t_argc], "-0.4" );
    strcpy ( t_argv[++t_argc], "--video.ntsc_setup.sharpness" );
    strcpy ( t_argv[++t_argc], "-0.8" );
    strcpy ( t_argv[++t_argc], "--video.ntsc_setup.burst_phase" );
    strcpy ( t_argv[++t_argc], "0.9" );
    strcpy ( t_argv[++t_argc], "--video.ntsc_setup.hue_warping" );
    strcpy ( t_argv[++t_argc], "1.0" );
    strcpy ( t_argv[++t_argc], "--sound.enable" );
    strcpy ( t_argv[++t_argc], "false" );
    strcpy ( t_argv[++t_argc], "--sound.frequency" );
    strcpy ( t_argv[++t_argc], "48000" );
    strcpy ( t_argv[++t_argc], "--sound.samples" );
    strcpy ( t_argv[++t_argc], "512" );
    strcpy ( t_argv[++t_argc], "--sound.buffer_size" );
    strcpy ( t_argv[++t_argc], "1.2" );
    strcpy ( t_argv[++t_argc], "--sound.gain" );
    strcpy ( t_argv[++t_argc], "75" );
    strcpy ( t_argv[++t_argc], "--input.misc.exit" );
    strcpy ( t_argv[++t_argc], "enable|key|f1|press" );
    strcpy ( t_argv[++t_argc], "--input.misc.back" );
    strcpy ( t_argv[++t_argc], "enable|key|a|press" );
    strcpy ( t_argv[++t_argc], "--input.misc.fullscreen" );
    strcpy ( t_argv[++t_argc], "enable|key|t|press" );
    strcpy ( t_argv[++t_argc], "--input.misc.debugger" );
    strcpy ( t_argv[++t_argc], "enable|key|f5|press" );
    for ( i = 1; i < 10; ++i ) {
        sprintf ( t_argv[++t_argc], "--input.misc.load.state.%d", i );
        sprintf ( t_argv[++t_argc], "enable|j0|b%d|press", i );
        sprintf ( t_argv[++t_argc], "--input.misc.save.state.%d", i );
        sprintf ( t_argv[++t_argc], "enable|j2|b%d|press", i );
    }
    strcpy ( t_argv[++t_argc], "--input.game.keyboard.key_c" );
    strcpy ( t_argv[++t_argc], "enable|key|d|press" );
    strcpy ( t_argv[++t_argc], "--input.game.keyboard.key_6" );
    strcpy ( t_argv[++t_argc], "enable|key|1|press" );
    strcpy ( t_argv[++t_argc], "--input.game.keyboard.key_tab" );
    strcpy ( t_argv[++t_argc], "enable|key|lctrl|press" );
    strcpy ( t_argv[++t_argc], "--input.game.keyboard.key_start" );
    strcpy ( t_argv[++t_argc], "enable|j0|b2|press" );
    strcpy ( t_argv[++t_argc], "--input.game.keyboard.key_option" );
    strcpy ( t_argv[++t_argc], "enable|c0|b_b|press" );
    strcpy ( t_argv[++t_argc], "--input.game.keyboard.key_select" );
    strcpy ( t_argv[++t_argc], "enable|c0|b_a|press" );
    strcpy ( t_argv[++t_argc], "--input.game.keyboard.key_reset" );
    strcpy ( t_argv[++t_argc], "enable|j0|b12|press" );
    strcpy ( t_argv[++t_argc], "--input.game.p0.joy.enable" );
    strcpy ( t_argv[++t_argc], "false" );
    strcpy ( t_argv[++t_argc], "--input.game.p1.joy.sensitivity" );
    strcpy ( t_argv[++t_argc], "91" );
    strcpy ( t_argv[++t_argc], "--input.game.p2.joy.deadzone" );
    strcpy ( t_argv[++t_argc], "19" );
    strcpy ( t_argv[++t_argc], "--input.game.p3.analog_stick_acts_digital" );
    strcpy ( t_argv[++t_argc], "true" );
    strcpy ( t_argv[++t_argc], "--input.game.p0.dpad.enable" );
    strcpy ( t_argv[++t_argc], "false" );
    strcpy ( t_argv[++t_argc], "--input.game.p1.dpad.analog.enable" );
    strcpy ( t_argv[++t_argc], "true" );
    strcpy ( t_argv[++t_argc], "--input.game.p2.dpad.analog.speed" );
    strcpy ( t_argv[++t_argc], "2" );
    strcpy ( t_argv[++t_argc], "--input.game.p3.mouse.enable" );
    strcpy ( t_argv[++t_argc], "false" );
    strcpy ( t_argv[++t_argc], "--input.game.p0.mouse.speed" );
    strcpy ( t_argv[++t_argc], "2" );
    strcpy ( t_argv[++t_argc], "--input.game.p0.auto_fire" );
    strcpy ( t_argv[++t_argc], "true" );
    strcpy ( t_argv[++t_argc], "--input.game.p0.auto_fire_speed" );
    strcpy ( t_argv[++t_argc], "5" );
    strcpy ( t_argv[++t_argc], "--input.game.p0.mouse.optimize" );
    strcpy ( t_argv[++t_argc], "cursor" );
    strcpy ( t_argv[++t_argc], "--input.game.p1.keyboard.enable" );
    strcpy ( t_argv[++t_argc], "false" );
    strcpy ( t_argv[++t_argc], "--input.game.p2.type" );
    strcpy ( t_argv[++t_argc], "Trackball" );
    strcpy ( t_argv[++t_argc], "--input.game.p3.800.type" );
    strcpy ( t_argv[++t_argc], "Paddles" );
    strcpy ( t_argv[++t_argc], "--input.game.p0.5200.type" );
    strcpy ( t_argv[++t_argc], "None" );
    strcpy ( t_argv[++t_argc], "--input.game.p0.joystick.left" );
    strcpy ( t_argv[++t_argc], "enable|j0|a2|-" );
    strcpy ( t_argv[++t_argc], "--input.game.p1.joystick.right" );
    strcpy ( t_argv[++t_argc], "enable|j0|d1|left" );
    strcpy ( t_argv[++t_argc], "--input.game.p2.joystick.up" );
    strcpy ( t_argv[++t_argc], "enable|j0|a1|+" );
    strcpy ( t_argv[++t_argc], "--input.game.p3.joystick.down" );
    strcpy ( t_argv[++t_argc], "enable|j2|a0|-" );
    strcpy ( t_argv[++t_argc], "--input.game.p0.joystick.bottom_button" );
    strcpy ( t_argv[++t_argc], "enable|c0|b_start|press" );
    strcpy ( t_argv[++t_argc], "--input.game.p1.joystick.button" );
    strcpy ( t_argv[++t_argc], "enable|j0|b8|press" );
    strcpy ( t_argv[++t_argc], "--input.game.p2.joystick.top_button" );
    strcpy ( t_argv[++t_argc], "enable|j0|b8|press" );
    strcpy ( t_argv[++t_argc], "--input.game.p3.joystick.key_start" );
    strcpy ( t_argv[++t_argc], "enable|j0|b10|press" );
    strcpy ( t_argv[++t_argc], "--input.game.p0.joystick.key_star" );
    strcpy ( t_argv[++t_argc], "enable|j3|b4|press" );
    strcpy ( t_argv[++t_argc], "--input.game.p1.joystick.key_0" );
    strcpy ( t_argv[++t_argc], "enable|j2|b0|press" );
    strcpy ( t_argv[++t_argc], "--input.game.p2.joystick.key_4" );
    strcpy ( t_argv[++t_argc], "enable|j0|b0|press|combo|j0|a3|-" );
    strcpy ( t_argv[++t_argc], "--input.game.p3.joystick.key_8" );
    strcpy ( t_argv[++t_argc], "enable|j2|b1|press|combo|j2|a3|-" );
    strcpy ( t_argv[++t_argc], "--input.game.p0.trackball.left" );
    strcpy ( t_argv[++t_argc], "enable|j0|a1|-" );
    strcpy ( t_argv[++t_argc], "--input.game.p1.trackball.right" );
    strcpy ( t_argv[++t_argc], "enable|j2|a1|-" );
    strcpy ( t_argv[++t_argc], "--input.game.p0.trackball.up" );
    strcpy ( t_argv[++t_argc], "enable|j1|a1|+" );
    strcpy ( t_argv[++t_argc], "--input.game.p1.trackball.down" );
    strcpy ( t_argv[++t_argc], "enable|j0|a2|+" );
    strcpy ( t_argv[++t_argc], "--input.game.p0.paddles.1.left" );
    strcpy ( t_argv[++t_argc], "enable|c0|a_lefty|+" );
    strcpy ( t_argv[++t_argc], "--input.game.p1.paddles.2.right" );
    strcpy ( t_argv[++t_argc], "enable|c0|a_leftx|-" );
    strcpy ( t_argv[++t_argc], "--input.game.p0.paddles.2.button" );
    strcpy ( t_argv[++t_argc], "enable|c0|a_rightx|-" );

    /*
     * Assign input values
     */
    logger_init();
    config_init(t_argc+1, p_argv);
    config_check_arguments ();

    /* Check Results */
    check_string ( "State File", config.general.state, "test.zip" );
    check_int ( "Quick State", config.general.quick_state, 8 );
    check_int ( "Start With", config.general.start_with, START_GUI_BROWSER );
    check_int ( "Throttle", config.general.throttle, 78 );
    check_int ( "SIO Patch", config.general.sio_patch, 0 );
    check_int ( "BASIC Enable", config.general.basic_enable, 1 );
    check_int ( "Machine", config.general.machine, MACHINE_TYPE_XL );
    check_int ( "Mapping", config.general.rom_mapping, CART_16K_ONE_CHIP );
    check_int ( "TV Standard", config.machine.tv_standard, PAL );
    check_int ( "RAM", config.machine.ram, 64 );
    check_string ( "Bios File", config.machine.bios, "bios.zip/bios.rom" );
    check_string ( "Profiles Dir", config.machine.profiles_dir, "C:\\Users\\Test\\Profs" );
    check_int ( "Fullscreen", config.video.fullscreen, 1 );
    check_int ( "Fixed Window", config.video.fixed_window, 1 );
    check_int ( "Width", config.video.width, 789 );
    check_int ( "Height", config.video.height, 567 );
    check_int ( "X", config.video.x, 56 );
    check_int ( "Y", config.video.y, 109 );
    check_int ( "Wide Playfield", config.video.wide_playfield, 1 );
    check_int ( "GUI Scale", config.video.gui_scale, 150 );
    check_int ( "Window Scaling", config.video.window_scaling, WINDOW_SCALING_FIT_WINDOW );
    check_int ( "Texture Scaling", config.video.texture_scaling, TEXTURE_SCALING_SMOOTH );
    check_string ( "Palette Dir", config.video.palette_name, "bright" );
    check_string ( "Shader", config.video.shader, "test_shader" );
    check_string ( "Backend", config.video.renderer_name, "opengl" );
    check_int ( "VSYNC", config.video.vsync, 0 );
    check_int ( "NTSC Artifacts", config.video.ntsc_artifacts, 1 );
    check_float ( "NTSC Hue", config.video.ntsc_setup.hue, 0.1 );
    check_float ( "NTSC Saturation", config.video.ntsc_setup.saturation, 0.5 );
    check_float ( "NTSC Contrast", config.video.ntsc_setup.contrast, -0.6 );
    check_float ( "NTSC Brightness", config.video.ntsc_setup.brightness, -0.4 );
    check_float ( "NTSC Sharpness", config.video.ntsc_setup.sharpness, -0.8 );
    check_float ( "NTSC Burst Phase", config.video.ntsc_setup.burst_phase, 0.9 );
    check_float ( "NTSC Hue Warping", config.video.ntsc_setup.hue_warping, 1.0 );
    check_int ( "Sound Enable", config.sound.enable, 0 );
    check_int ( "Sound Freq", config.sound.frequency, 48000 );
    check_int ( "Sound Samples", config.sound.samples, 512 );
    check_float ( "Sound Buffer", config.sound.buffer_size, 1.2 );
    check_int ( "Sound Gain", config.sound.gain, 75 );
    check_sound_buffer_save_load ();
    check_int ( "Mapping Parse 8-bit Lower",
            media_if_get_mapping_from_string("8bit.16k"), CART_PC_16K );
    strcpy ( mapping_input, "8BIT.16K" );
    check_int ( "Mapping Parse 8-bit Mixed",
            media_if_get_mapping_from_string(mapping_input), CART_PC_16K );
    check_string ( "Mapping Input Not Mutated", mapping_input, "8BIT.16K" );
    check_int ( "Mapping Parse Auto",
            media_if_get_mapping_from_string("auto"), CART_MIN );
    check_int ( "Mapping Parse Empty",
            media_if_get_mapping_from_string(""), CART_MIN );
    check_int ( "Mapping Parse Invalid",
            media_if_get_mapping_from_string("bad.mapping"), CART_MIN );
    check_string ( "Mapping Phoenix String",
            media_if_get_string_from_mapping(CART_PHOENIX), "8bit.phoenix" );

    /* Check Input Results */
    input_answer.enable = 1;
    input_answer.device[0] = DEV_KEYBOARD;
    input_answer.device_num[0] = 0;
    input_answer.part_type[0] = PART_TYPE_KEY;
    input_answer.part_num[0] = pc_interpret_key("f1");
    input_answer.direction[0] = DIR_PRESS;
    input_answer.combo = 0;
    check_input ( "Input.UI.Exit", &config.input.ui.exit, &input_answer );

    input_answer.part_num[0] = pc_interpret_key("a");
    check_input ( "Input.Misc.Back", &config.input.ui.back, &input_answer );
    input_answer.part_num[0] = pc_interpret_key("t");
    check_input ( "Input.Misc.Fullscreen", &config.input.ui.fullscreen, &input_answer );
    input_answer.part_num[0] = pc_interpret_key("f5");
    check_input ( "Input.Misc.Debugger", &config.input.ui.debugger, &input_answer );

    input_answer.device[0] = DEV_JOYSTICK;
    input_answer.part_type[0] = PART_TYPE_BUTTON;
    for ( i = 1; i < 10; ++i ) {
        input_answer.part_num[0] = i;
        input_answer.device_num[0] = 0;
        check_input ( "Input.Misc.State Load", &config.input.ui.state_load[i-1], &input_answer );
        input_answer.device_num[0] = 2;
        check_input ( "Input.Misc.State Save", &config.input.ui.state_save[i-1], &input_answer );
    }

    input_answer.device[0] = DEV_KEYBOARD;
    input_answer.device_num[0] = 0;
    input_answer.part_type[0] = PART_TYPE_KEY;
    input_answer.part_num[0] = pc_interpret_key("d");
    check_input ( "Input.Game.Keyboard C", &config.input.game.keyboard[A800_KEY_C], &input_answer );
    input_answer.part_num[0] = pc_interpret_key("1");
    check_input ( "Input.Game.Keyboard 6", &config.input.game.keyboard[A800_KEY_6], &input_answer );
    input_answer.part_num[0] = pc_interpret_key("lctrl");
    check_input ( "Input.Game.Keyboard Tab", &config.input.game.keyboard[A800_KEY_TAB], &input_answer );

    input_answer.device[0] = DEV_JOYSTICK;
    input_answer.part_type[0] = PART_TYPE_BUTTON;
    input_answer.part_num[0] = 2;
    check_input ( "Input.Game.Keyboard Start", &config.input.game.start_key, &input_answer );

    input_answer.device[0] = DEV_CONTROLLER;
    input_answer.part_num[0] = pc_interpret_controller_button ( "b" );
    check_input ( "Input.Game.Keyboard Option", &config.input.game.option_key, &input_answer );
    input_answer.part_num[0] = pc_interpret_controller_button ( "a" );
    check_input ( "Input.Game.Keyboard Select", &config.input.game.select_key, &input_answer );

    input_answer.device[0] = DEV_JOYSTICK;
    input_answer.part_num[0] = 12;
    check_input ( "Input.Game.Keyboard Reset", &config.input.game.reset_key, &input_answer );

    check_int ( "Input.P0.Joy.Enable", config.input.game.players[0].joy_enable, 0 );
    check_int ( "Input.P1.Joy.Sensitivity", config.input.game.players[1].joy_sensitivity, 91 );
    check_int ( "Input.P2.Joy.Deadzone", config.input.game.players[2].joy_deadzone, 19 );
    check_int ( "Input.P3.Joy.AnalogStickActsDigital", config.input.game.players[3].analog_stick_acts_digital, 1 );
    check_int ( "Input.P0.Dpad.Enable", config.input.game.players[0].dpad_enable, 0 );
    check_int ( "Input.P1.Dpad.Analog.Enable", config.input.game.players[1].digdir_analog_enable, 1 );
    check_int ( "Input.P2.Dpad.Analog.Speed", config.input.game.players[2].digdir_analog_speed, 2 );
    check_int ( "Input.P3.Mouse.Enable", config.input.game.players[3].mouse_enable, 0 );
    check_int ( "Input.P0.Mouse.Speed", config.input.game.players[0].mouse_speed, 2 );
    check_int ( "Input.P0.Auto.Fire", config.input.game.players[0].auto_fire, 1 );
    check_int ( "Input.P0.Auto.Fire.Speed", config.input.game.players[0].auto_fire_speed, 5 );
    check_int ( "Input.P0.Mouse.Optimize", config.input.game.players[0].mouse_optimize, MOUSE_OPTIMIZE_CURSOR );
    check_int ( "Input.P1.Keyboard.Enable", config.input.game.players[1].keyboard_enable, 0 );
    check_int ( "Input.P0.Type", config.input.game.players[0].type, 0 );
    check_int ( "Input.P2.Type", config.input.game.players[2].type, CTRLR_TYPE_TRACKBALL );
    check_int ( "Input.P3.Type", config.input.game.players[3].type, CTRLR_TYPE_PADDLES );

    input_answer.device[0] = DEV_JOYSTICK;
    input_answer.part_type[0] = PART_TYPE_AXIS;
    input_answer.part_num[0] = 2;
    input_answer.direction[0] = DIR_MINUS;
    check_input ( "Input.Game.P0.Joystick.Left", &config.input.game.players[0].stick[DIR_LEFT], &input_answer );
    input_answer.part_type[0] = PART_TYPE_HAT;
    input_answer.part_num[0] = 1;
    input_answer.direction[0] = DIR_LEFT;
    check_input ( "Input.Game.P1.Joystick.Right", &config.input.game.players[1].stick[DIR_RIGHT], &input_answer );
    input_answer.part_type[0] = PART_TYPE_AXIS;
    input_answer.part_num[0] = 1;
    input_answer.direction[0] = DIR_PLUS;
    check_input ( "Input.Game.P2.Joystick.Up", &config.input.game.players[2].stick[DIR_UP], &input_answer );
    input_answer.device_num[0] = 2;
    input_answer.part_num[0] = 0;
    input_answer.direction[0] = DIR_MINUS;
    check_input ( "Input.Game.P3.Joystick.Down", &config.input.game.players[3].stick[DIR_DOWN], &input_answer );

    input_answer.device[0] = DEV_CONTROLLER;
    input_answer.device_num[0] = 0;
    input_answer.part_type[0] = PART_TYPE_BUTTON;
    input_answer.part_num[0] = pc_interpret_controller_button ( "start" );
    input_answer.direction[0] = DIR_PRESS;
    check_input ( "Input.Game.P0.Joystick.BottomButton", &config.input.game.players[0].bottom_button, &input_answer );
    input_answer.device[0] = DEV_JOYSTICK;
    input_answer.part_num[0] = 8;
    check_input ( "Input.Game.P1.Joystick.Button", &config.input.game.players[1].bottom_button, &input_answer );
    check_input ( "Input.Game.P2.Joystick.TopButton", &config.input.game.players[2].top_button, &input_answer );
    input_answer.part_num[0] = 10;
    check_input ( "Input.Game.P3.Joystick.KeyStart", &config.input.game.players[3].keypad[KEY_START>>1], &input_answer );
    input_answer.device_num[0] = 3;
    input_answer.part_num[0] = 4;
    check_input ( "Input.Game.P3.Joystick.KeyStar", &config.input.game.players[0].keypad[KEY_STAR>>1], &input_answer );
    input_answer.device_num[0] = 2;
    input_answer.part_num[0] = 0;
    check_input ( "Input.Game.P3.Joystick.Key0", &config.input.game.players[1].keypad[KEY_0>>1], &input_answer );
    input_answer.device_num[0] = 0;
    input_answer.part_num[0] = 0;
    input_answer.combo = 1;
    input_answer.device[1] = DEV_JOYSTICK;
    input_answer.device_num[1] = 0;
    input_answer.part_type[1] = PART_TYPE_AXIS;
    input_answer.part_num[1] = 3;
    input_answer.direction[1] = DIR_MINUS;
    check_input ( "Input.Game.P3.Joystick.Key4", &config.input.game.players[2].keypad[KEY_4>>1], &input_answer );
    input_answer.device_num[0] = 2;
    input_answer.part_num[0] = 1;
    input_answer.device_num[1] = 2;
    check_input ( "Input.Game.P3.Joystick.Key8", &config.input.game.players[3].keypad[KEY_8>>1], &input_answer );

    input_answer.combo = 0;
    input_answer.device_num[0] = 0;
    input_answer.part_type[0] = PART_TYPE_AXIS;
    input_answer.part_num[0] = 1;
    input_answer.direction[0] = DIR_MINUS;
    check_input ( "Input.Game.P0.Trackball.Left", &config.input.game.players[0].trackball[DIR_LEFT], &input_answer );
    input_answer.device_num[0] = 2;
    check_input ( "Input.Game.P1.Trackball.Right", &config.input.game.players[1].trackball[DIR_RIGHT], &input_answer );
    input_answer.device_num[0] = 1;
    input_answer.direction[0] = DIR_PLUS;
    check_input ( "Input.Game.P0.Trackball.Up", &config.input.game.players[0].trackball[DIR_UP], &input_answer );
    input_answer.device_num[0] = 0;
    input_answer.part_num[0] = 2;
    input_answer.direction[0] = DIR_PLUS;
    check_input ( "Input.Game.P1.Trackball.Down", &config.input.game.players[1].trackball[DIR_DOWN], &input_answer );

    input_answer.device[0] = DEV_CONTROLLER;
    input_answer.device_num[0] = 0;
    input_answer.part_type[0] = PART_TYPE_AXIS;
    input_answer.part_num[0] = pc_interpret_controller_axis ( "lefty" );
    input_answer.direction[0] = DIR_PLUS;
    check_input ( "Input.Game.P0.Paddles.1.Left", &config.input.game.players[0].paddles[0].left, &input_answer );
    input_answer.part_num[0] = pc_interpret_controller_axis ( "leftx" );
    input_answer.direction[0] = DIR_MINUS;
    check_input ( "Input.Game.P1.Paddles.2.Right", &config.input.game.players[1].paddles[1].right, &input_answer );
    input_answer.part_num[0] = pc_interpret_controller_axis ( "rightx" );
    check_input ( "Input.Game.P0.Paddles.2.Button", &config.input.game.players[0].paddles[1].button, &input_answer );

    /* Recovery options (--shader / --backend / --safe).  Runs last because the
       ROM case re-enters config_init. */
    check_recovery_options ();

    if ( !failure )
        output_result ( 1, "Args", "All Tests Passed", "" );

    config_exit();
    logger_exit();

    return failure;

} /* end main */
