/******************************************************************************
*
* 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_cpu.c
*
* DESCRIPTION:  This contains tests for the cpu module.
*
* AUTHORS:  bberlin
*
* CHANGE LOG
*
*  VER     DATE     CHANGED BY   DESCRIPTION OF CHANGE
* ------  --------  ----------   ------------------------------------
* 0.9.0   12/31/19  bberlin      Creation
******************************************************************************/
#define SDL_DISABLE_OLD_NAMES
#include <SDL3/SDL.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "../src/interface/input.h"
#include "../src/interface/logger.h"
#include "../src/interface/sdl_if.h"
#include "../src/core/memory.h"
#include "../src/kat5200.h"

/*
 * Keep the older test vocabulary readable while targeting the SDL3 event and
 * gamepad names used by the emulator proper.
 */
#define SDL_KEYDOWN SDL_EVENT_KEY_DOWN
#define SDL_KEYUP SDL_EVENT_KEY_UP
#define SDL_JOYAXISMOTION SDL_EVENT_JOYSTICK_AXIS_MOTION
#define SDL_JOYHATMOTION SDL_EVENT_JOYSTICK_HAT_MOTION
#define SDL_JOYBUTTONDOWN SDL_EVENT_JOYSTICK_BUTTON_DOWN
#define SDL_JOYBUTTONUP SDL_EVENT_JOYSTICK_BUTTON_UP
#define SDL_JOYBALLMOTION SDL_EVENT_JOYSTICK_BALL_MOTION
#define SDL_MOUSEMOTION SDL_EVENT_MOUSE_MOTION
#define SDL_MOUSEBUTTONDOWN SDL_EVENT_MOUSE_BUTTON_DOWN
#define SDL_MOUSEBUTTONUP SDL_EVENT_MOUSE_BUTTON_UP
#define SDL_CONTROLLERAXISMOTION SDL_EVENT_GAMEPAD_AXIS_MOTION
#define SDL_CONTROLLERBUTTONDOWN SDL_EVENT_GAMEPAD_BUTTON_DOWN
#define SDL_CONTROLLERBUTTONUP SDL_EVENT_GAMEPAD_BUTTON_UP
#define SDL_CONTROLLER_BUTTON_DPAD_UP SDL_GAMEPAD_BUTTON_DPAD_UP
#define SDL_CONTROLLER_BUTTON_DPAD_DOWN SDL_GAMEPAD_BUTTON_DPAD_DOWN
#define SDL_CONTROLLER_BUTTON_DPAD_LEFT SDL_GAMEPAD_BUTTON_DPAD_LEFT
#define SDL_CONTROLLER_BUTTON_DPAD_RIGHT SDL_GAMEPAD_BUTTON_DPAD_RIGHT
#define SDL_CONTROLLER_BUTTON_START SDL_GAMEPAD_BUTTON_START
#define SDL_CONTROLLER_BUTTON_LEFTSHOULDER SDL_GAMEPAD_BUTTON_LEFT_SHOULDER
#define SDL_CONTROLLER_BUTTON_RIGHTSHOULDER SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER
#define SDL_CONTROLLER_BUTTON_A SDL_GAMEPAD_BUTTON_SOUTH
#define SDL_CONTROLLER_BUTTON_B SDL_GAMEPAD_BUTTON_EAST
#define SDL_CONTROLLER_BUTTON_X SDL_GAMEPAD_BUTTON_WEST
#define SDL_CONTROLLER_BUTTON_Y SDL_GAMEPAD_BUTTON_NORTH
#define SDL_CONTROLLER_AXIS_RIGHTX SDL_GAMEPAD_AXIS_RIGHTX
#define SDL_CONTROLLER_AXIS_RIGHTY SDL_GAMEPAD_AXIS_RIGHTY
#define caxis gaxis
#define cbutton gbutton

void test_key_input ( void );
void test_joystick_input ( void );
void test_controller_input ( void );
void test_mouse_input ( void );
void test_misc_input ( void );
void test_none_controller_input ( void );
void test_auto_fire_input ( void );
void test_pokey_5200_keypad_latch ( void );
void test_pokey_pot_config_values ( void );
void test_ui_back_controller_input ( void );
void test_unmapped_gui_button_input ( void );

int input_key_down_stick (void *input, int player, int dir, int key);
int input_key_up_stick (void *input, int player, int dir, int key);
int input_stick_h_axis ( int player, int reading );
int input_stick_v_axis ( int player, int reading );

extern t_sdl_if g_pc_if;
extern t_pc_stick_interface g_stick_interface[MAX_CONTROLLER];
extern t_pc_input g_pc_input;

extern t_pokey pokey;
extern t_pia pia;
extern t_gtia gtia;

KBYTE pc_old = 0;
int failure = 0;
char current_file[1024];
SDL_Event event;
static int test_gui_back_count = 0;

static int test_gui_back_func ( int debug_flag ) {

    (void)debug_flag;
    test_gui_back_count++;
    return 0;

} /* end test_gui_back_func */

/*
 * Neutralize a UI hotkey binding so it does not intercept a PC key/button that
 * the key-matrix tests also drive.  The test conf binds esc->back, f1->debugger
 * and j0 button 4->back, and the UI handler (registered last) would otherwise
 * win over the Atari key.  Drop the binding list and point the head at an
 * unused device so input_setup_ui_function() registers nothing for it.
 */
static void disable_ui_binding ( t_input_common *ui ) {

    t_input_common *p = ui->next, *next;

    while ( p ) {
        next = p->next;
        free ( p );
        p = next;
    }
    ui->next = 0;
    ui->device[0] = DEV_MAX;
}

#define TEST_AXIS_H 0
#define TEST_AXIS_V 1

static int test_clamp_pot_value ( int value ) {

    if ( value < 0 )
        return 0;
    if ( value > 227 )
        return 227;
    return value;
}

static int test_pot_center ( int player, int axis, int trackball ) {

    return trackball ?
        g_stick_interface[player].trackball_center[axis] :
        g_stick_interface[player].pot_center[axis];
}

static int test_pot_low ( int player, int axis, int trackball ) {

    if ( trackball )
        return test_clamp_pot_value (
                g_stick_interface[player].trackball_center[axis] -
                g_stick_interface[player].trackball_dpot_max );
    return g_stick_interface[player].pot_low[axis];
}

static int test_pot_high ( int player, int axis, int trackball ) {

    if ( trackball )
        return test_clamp_pot_value (
                g_stick_interface[player].trackball_center[axis] +
                g_stick_interface[player].trackball_dpot_max );
    return g_stick_interface[player].pot_high[axis];
}

static int test_pot_lut ( int player, int axis, int reading ) {

    return g_stick_interface[player].pot_values[axis][32768 + reading];
}

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 );
    }

}

void reg_test ( char *name, char *format, KBYTE *reg, KBYTE value ) {

    char error[257] = "";
    char res_string[257] = "";
    char my_string[50];

    sprintf ( res_string, format, *reg );
    int result = (*reg == value) ? 1: 0;
    if (!result) {
        sprintf ( my_string, format, value );
        sprintf ( error, "Should be %s", my_string );
    }

    output_result ( result, name, res_string, error );
}

void int_test ( char *name, char *format, int reg, int value ) {

    char error[257] = "";
    char res_string[257] = "";
    char my_string[50];

    sprintf ( res_string, format, reg );
    int result = (reg == value) ? 1: 0;
    if (!result) {
        sprintf ( my_string, format, value );
        sprintf ( error, "Should be %s", my_string );
    }

    output_result ( result, name, res_string, error );
}

void key_test ( int tmp_event, int mod, int key, char *name, char *format, KBYTE *reg, KBYTE value ) {

    char error[257] = "";
    char res_string[257] = "";
    char my_string[50];
    event.type = tmp_event;
    event.key.key = key;
    event.key.mod = mod;
    SDL_PushEvent(&event);
    pc_poll_input_events ( &event );

    sprintf ( res_string, format, *reg );
    int result = (*reg == value) ? 1: 0;
    if (!result) {
        sprintf ( my_string, format, value );
        sprintf ( error, "Should be %s", my_string );
    }

    output_result ( result, name, res_string, error );
}

void axis_test ( int tmp_event, int tmp_joystick, int tmp_axis, int axis_value, char *name, char *format, KBYTE *reg, KBYTE value ) {

    char error[257] = "";
    char res_string[257] = "";
    char my_string[50];
    event.type = tmp_event;
    if ( tmp_event == SDL_JOYAXISMOTION ) {
        event.jaxis.which = tmp_joystick;
        event.jaxis.axis = tmp_axis;
        event.jaxis.value = axis_value;
    }
    else {
        event.caxis.which = tmp_joystick;
        event.caxis.axis = tmp_axis;
        event.caxis.value = axis_value;
    }
    SDL_PushEvent(&event);
    pc_poll_input_events ( &event );

    sprintf ( res_string, format, *reg );
    int result = (*reg == value) ? 1: 0;
    if (!result) {
        sprintf ( my_string, format, value );
        sprintf ( error, "Should be %s", my_string );
    }

    output_result ( result, name, res_string, error );
}

void dpad_test ( int tmp_joystick, int hat_value, char *name, char *format, KBYTE *reg, KBYTE value ) {

    char error[257] = "";
    char res_string[257] = "";
    char my_string[50];
    event.type = SDL_JOYHATMOTION;
    event.jhat.value = hat_value;
    SDL_PushEvent(&event);
    pc_poll_input_events ( &event );

    sprintf ( res_string, format, *reg );
    int result = (*reg == value) ? 1: 0;
    if (!result) {
        sprintf ( my_string, format, value );
        sprintf ( error, "Should be %s", my_string );
    }

    output_result ( result, name, res_string, error );
}

void button_test ( int tmp_event, int tmp_joystick, int tmp_button, char *name, char *format, KBYTE *reg, KBYTE value ) {

    char error[257] = "";
    char res_string[257] = "";
    char my_string[50];
    event.type = tmp_event;
    if (tmp_event == SDL_JOYBUTTONDOWN || tmp_event == SDL_JOYBUTTONUP) {
        event.jbutton.which = tmp_joystick;
        event.jbutton.button = tmp_button;
    }
    else if (tmp_event == SDL_CONTROLLERBUTTONDOWN ||
            tmp_event == SDL_CONTROLLERBUTTONUP) {
        event.cbutton.which = tmp_joystick;
        event.cbutton.button = tmp_button;
    }
    else
        event.button.button = tmp_button;
    SDL_PushEvent(&event);
    pc_poll_input_events ( &event );

    sprintf ( res_string, format, *reg );
    int result = (*reg == value) ? 1: 0;
    if (!result) {
        sprintf ( my_string, format, value );
        sprintf ( error, "Should be %s", my_string );
    }

    output_result ( result, name, res_string, error );
}

void push_ball_event ( int tmp_event, int tmp_joystick, int tmp_ball, float x_value, float y_value ) {

    event.type = tmp_event;
    if ( tmp_event == SDL_JOYBALLMOTION ) {
        event.jball.which = tmp_joystick;
        event.jball.ball = tmp_ball;
    }
    event.motion.xrel = x_value;
    event.motion.yrel = y_value;
    SDL_PushEvent(&event);
    pc_poll_input_events ( &event );
}

void set_mouse_axis_input ( t_input_common *input, int axis, e_direction direction ) {

    memset ( input, 0, sizeof(*input) );
    input->enable = 1;
    input->device[0] = DEV_MOUSE;
    input->part_type[0] = PART_TYPE_AXIS;
    input->part_num[0] = axis;
    input->direction[0] = direction;
}

void set_mouse_button_input ( t_input_common *input, int button ) {

    memset ( input, 0, sizeof(*input) );
    input->enable = 1;
    input->device[0] = DEV_MOUSE;
    input->part_type[0] = PART_TYPE_BUTTON;
    input->part_num[0] = button;
    input->direction[0] = DIR_PRESS;
}

void set_joystick_button_input ( t_input_common *input, int device_num,
        int button ) {

    memset ( input, 0, sizeof(*input) );
    input->enable = 1;
    input->device[0] = DEV_JOYSTICK;
    input->device_num[0] = device_num;
    input->part_type[0] = PART_TYPE_BUTTON;
    input->part_num[0] = button;
    input->direction[0] = DIR_PRESS;
}

void set_controller_button_input ( t_input_common *input, int device_num,
        int button ) {

    memset ( input, 0, sizeof(*input) );
    input->enable = 1;
    input->device[0] = DEV_CONTROLLER;
    input->device_num[0] = device_num;
    input->part_type[0] = PART_TYPE_BUTTON;
    input->part_num[0] = button;
    input->direction[0] = DIR_PRESS;
}

static void set_controller_button_combo_input ( t_input_common *input,
        int device_num, int button, int combo_button ) {

    set_controller_button_input ( input, device_num, button );
    input->combo = 1;
    input->device[1] = DEV_CONTROLLER;
    input->device_num[1] = device_num;
    input->part_type[1] = PART_TYPE_BUTTON;
    input->part_num[1] = combo_button;
    input->direction[1] = DIR_PRESS;
}

static void disable_input_chain ( t_input_common *input ) {

    t_input_common *p = input->next, *next;

    while ( p ) {
        next = p->next;
        free ( p );
        p = next;
    }

    memset ( input, 0, sizeof(*input) );
    input->device[0] = DEV_MAX;
}

static void disable_player_inputs ( t_player_input *player ) {

    int i;

    for ( i = 0; i < MAX_DIR; ++i ) {
        disable_input_chain ( &player->stick[i] );
        disable_input_chain ( &player->trackball[i] );
    }

    for ( i = 0; i < 2; ++i ) {
        disable_input_chain ( &player->paddles[i].left );
        disable_input_chain ( &player->paddles[i].right );
        disable_input_chain ( &player->paddles[i].button );
    }

    disable_input_chain ( &player->top_button );
    disable_input_chain ( &player->bottom_button );

    for ( i = 0; i < MAX_KEYPAD_KEYS; ++i )
        disable_input_chain ( &player->keypad[i] );
}

static void reset_gamepad_mapping_test_config ( void ) {

    int i;
    t_config *p_config = config_get_global_cfg();

    for ( i = 0; i < MAX_CONTROLLER; ++i ) {
        disable_player_inputs ( &p_config->input.game.players[i] );
        p_config->input.game.players[i].type = CTRLR_TYPE_NONE;
        p_config->input.game.players[i].joy_enable = 0;
    }
}

static void make_gamepad_button_event ( SDL_Event *event, int instance_id,
        int button ) {

    memset ( event, 0, sizeof(*event) );
    event->type = SDL_EVENT_GAMEPAD_BUTTON_DOWN;
    event->gbutton.which = instance_id;
    event->gbutton.button = button;
}

void test_ui_back_controller_input ( void ) {

    char error[257] = "";
    char res_string[257] = "";
    t_config *p_config = config_get_global_cfg();

    p_config->input.game.players[0].joy_enable = 0;
    set_controller_button_input ( &p_config->input.ui.back, 3,
            SDL_GAMEPAD_BUTTON_EAST );
    input_set_gui_function ( test_gui_back_func );
    input_set_controls();

    test_gui_back_count = 0;
    event.type = SDL_CONTROLLERBUTTONDOWN;
    event.cbutton.which = 3;
    event.cbutton.button = SDL_GAMEPAD_BUTTON_EAST;
    pc_poll_input_events ( &event );

    sprintf ( res_string, "back_count=%d", test_gui_back_count );
    sprintf ( error, "UI Back controller binding should ignore player enable" );
    output_result ( test_gui_back_count == 1,
            "UI Back Controller Binding", res_string, error );

} /* end test_ui_back_controller_input */

static void mapped_button_test ( const char *name, int event_slot,
        int event_button, int expected_consumed, int expected_back_count ) {

    char error[257] = "";
    char res_string[257] = "";
    SDL_Event button_event;
    int status = -1;
    int consumed;
    const int event_instance_id = 2000 + event_slot;

    pc_set_joystick_id ( event_slot, event_instance_id );
    kat5200_set_status ( KSTATUS_RUN );
    input_set_gui_function ( test_gui_back_func );
    test_gui_back_count = 0;

    make_gamepad_button_event ( &button_event, event_instance_id,
            event_button );
    consumed = pc_handle_unmapped_gui_button ( &button_event, &status );

    sprintf ( res_string, "consumed=%d back_count=%d status=%d",
            consumed, test_gui_back_count, status );
    sprintf ( error, "Expected consumed=%d back_count=%d",
            expected_consumed, expected_back_count );
    output_result ( consumed == expected_consumed &&
            test_gui_back_count == expected_back_count, name, res_string,
            error );
}

static void configure_player0_bottom_button ( int button ) {

    t_config *p_config = config_get_global_cfg();
    t_player_input *player = &p_config->input.game.players[0];

    player->type = CTRLR_TYPE_JOYSTICK;
    player->joy_enable = 1;
    set_controller_button_input ( &player->bottom_button, 0, button );
}

static void configure_player0_bottom_button_combo ( int button,
        int combo_button ) {

    t_config *p_config = config_get_global_cfg();
    t_player_input *player = &p_config->input.game.players[0];

    player->type = CTRLR_TYPE_JOYSTICK;
    player->joy_enable = 1;
    set_controller_button_combo_input ( &player->bottom_button, 0, button,
            combo_button );
}

void test_unmapped_gui_button_input ( void ) {

    t_config *p_config = config_get_global_cfg();

    reset_gamepad_mapping_test_config();
    p_config->general.machine = MACHINE_TYPE_5200;
    input_set_controls();
    mapped_button_test ( "Unmapped Gamepad B Opens GUI", 5,
            SDL_GAMEPAD_BUTTON_EAST, 1, 1 );

    reset_gamepad_mapping_test_config();
    p_config->general.machine = MACHINE_TYPE_5200;
    input_set_controls();
    mapped_button_test ( "Unmapped Gamepad Non-B Ignored", 5,
            SDL_GAMEPAD_BUTTON_SOUTH, 0, 0 );

    reset_gamepad_mapping_test_config();
    p_config->general.machine = MACHINE_TYPE_5200;
    configure_player0_bottom_button ( SDL_GAMEPAD_BUTTON_EAST );
    input_set_controls();
    mapped_button_test ( "Mapped Gamepad B Blocks GUI", 0,
            SDL_GAMEPAD_BUTTON_EAST, 0, 0 );

    reset_gamepad_mapping_test_config();
    p_config->general.machine = MACHINE_TYPE_5200;
    configure_player0_bottom_button ( SDL_GAMEPAD_BUTTON_SOUTH );
    input_set_controls();
    mapped_button_test ( "Mapped Gamepad Non-B Still Ignores B Fallback", 0,
            SDL_GAMEPAD_BUTTON_SOUTH, 0, 0 );

    reset_gamepad_mapping_test_config();
    p_config->general.machine = MACHINE_TYPE_5200;
    input_set_controls();
    kat5200_set_status ( KSTATUS_RUN );
    input_set_gui_function ( test_gui_back_func );
    test_gui_back_count = 0;
    {
        SDL_Event button_event;
        int status = -1;
        int consumed;

        make_gamepad_button_event ( &button_event, 9999,
                SDL_GAMEPAD_BUTTON_EAST );
        consumed = pc_handle_unmapped_gui_button ( &button_event, &status );
        output_result ( consumed == 0 && test_gui_back_count == 0,
                "Unknown Gamepad B Ignored", "consumed=0 back_count=0",
                "Unknown slot should not open GUI" );
    }

    reset_gamepad_mapping_test_config();
    p_config->general.machine = MACHINE_TYPE_5200;
    configure_player0_bottom_button_combo ( SDL_GAMEPAD_BUTTON_SOUTH,
            SDL_GAMEPAD_BUTTON_EAST );
    input_set_controls();
    mapped_button_test ( "Gamepad B Combo Modifier Blocks GUI", 0,
            SDL_GAMEPAD_BUTTON_EAST, 0, 0 );

    reset_gamepad_mapping_test_config();
    p_config->general.machine = MACHINE_TYPE_5200;
    configure_player0_bottom_button_combo ( SDL_GAMEPAD_BUTTON_EAST,
            SDL_GAMEPAD_BUTTON_SOUTH );
    input_set_controls();
    mapped_button_test ( "Gamepad B Combo Action Blocks GUI", 0,
            SDL_GAMEPAD_BUTTON_EAST, 0, 0 );

    kat5200_set_status ( 0 );

} /* end test_unmapped_gui_button_input */

void ball_test ( int tmp_event, int tmp_joystick, int tmp_ball, float x_value, float y_value, char *name, char *format, KBYTE *reg, KBYTE value ) {

    char error[257] = "";
    char res_string[257] = "";
    char my_string[50];
    push_ball_event ( tmp_event, tmp_joystick, tmp_ball, x_value, y_value );
    input_flush_mouse_motion();

    sprintf ( res_string, format, *reg );
    int result = (*reg == value) ? 1: 0;
    if (!result) {
        sprintf ( my_string, format, value );
        sprintf ( error, "Should be %s", my_string );
    }

    output_result ( result, name, res_string, error );
}

/******************************************************************************
**  Function   :  main
**
**  Objective  :  This is the main used to test the input module
**
**  Parameters :  NONE
**
**  return     :  0 if success else number of failures

******************************************************************************/
int main ( int argc, char *argv[]  ) {

    int i,j, pot, value;
    FILE *fp;
    char name[256] = "";
    char *testv[1] = {"kat5200"};
    t_config *p_config = config_get_global_cfg();

    /*
     * Assign input values
     */
    config_init(1, testv);

    /* if we input machine type, set it */
    if ( argc > 1 ) {
        if ( strstr(argv[1], "5200") )
            p_config->general.machine = MACHINE_TYPE_5200;
        if ( strstr(argv[1], "800") )
            p_config->general.machine = MACHINE_TYPE_800;
        if ( strstr(argv[1], "xl") )
            p_config->general.machine = MACHINE_TYPE_XL;
    }

    config_load_game_input (p_config->general.machine, config_get_global_table(),
        "All" );

    logger_init();
    console_init();
    console_reset ( p_config->general.machine, p_config->machine.tv_standard,
            p_config->machine.ram);
    input_init();

    /*
     * No real joysticks are connected under test, so seed the instance-id ->
     * device-index table that pc_get_joystick_index_from_id() reads.  The
     * joystick tests push synthetic events with which == player index, so map
     * id N to slot N.
     */
    for ( i = 0; i < MAX_PC_DEVICE; ++i )
        pc_set_joystick_id ( i, i );

    /*
     * The key-matrix tests drive PC keys/buttons that the test conf also binds
     * to UI hotkeys (esc->back, f1->debugger, j0 button 4->back).  Disable those
     * UI bindings and rebuild the control tables so the Atari input registers.
     */
    disable_ui_binding ( &p_config->input.ui.back );
    disable_ui_binding ( &p_config->input.ui.debugger );
    input_set_controls();

    /*
     * Go through config file input settings, push events and check them
     */
    test_pokey_pot_config_values();
    test_key_input();
    test_joystick_input();
    test_controller_input();
    test_mouse_input();
    test_misc_input();
    test_auto_fire_input();
    test_none_controller_input();
    test_pokey_5200_keypad_latch();
    test_ui_back_controller_input();
    test_unmapped_gui_button_input();

    console_exit();
    config_exit();
    input_exit();
    logger_exit();
    input_exit();

    return failure;

} /* end main */

/*
 * test_pokey_pot_config_values - direct overrides for editable POT settings
 */
void test_pokey_pot_config_values ( void ) {

    char name[257];
    t_config *p_config = config_get_global_cfg();
    t_machine saved_machine = p_config->machine;
    t_player_input saved_p0 = p_config->input.game.players[0];
    int h_center = p_config->general.machine == MACHINE_TYPE_5200 ? 100 : 120;
    int v_center = p_config->general.machine == MACHINE_TYPE_5200 ? 101 : 121;
    int h_low = p_config->general.machine == MACHINE_TYPE_5200 ? 10 : 8;
    int h_high = p_config->general.machine == MACHINE_TYPE_5200 ? 190 : 200;
    int v_low = p_config->general.machine == MACHINE_TYPE_5200 ? 20 : 9;
    int v_high = p_config->general.machine == MACHINE_TYPE_5200 ? 180 : 201;
    int saved_controller_type = pokey.controller_type[0];
    KBYTE saved_pot_enable = gtia.pot_enable;
    KBYTE read_value;

    p_config->machine.joystick_hpot_center = h_center;
    p_config->machine.joystick_vpot_center = v_center;
    p_config->machine.joystick_hpot_low = h_low;
    p_config->machine.joystick_hpot_high = h_high;
    p_config->machine.joystick_vpot_low = v_low;
    p_config->machine.joystick_vpot_high = v_high;
    p_config->input.game.players[0].type = CTRLR_TYPE_JOYSTICK;
    p_config->input.game.players[0].joy_sensitivity = 100;
    input_set_controls();

    sprintf ( name, "POT Config P0 HPOT Center Cache" );
    int_test ( name, "%d", g_stick_interface[0].pot_center[TEST_AXIS_H], h_center );
    sprintf ( name, "POT Config P0 VPOT Center Cache" );
    int_test ( name, "%d", g_stick_interface[0].pot_center[TEST_AXIS_V], v_center );
    sprintf ( name, "POT Config P0 HPOT Init" );
    reg_test ( name, "%d", &pokey.pc_pot[0], h_center );
    sprintf ( name, "POT Config P0 VPOT Init" );
    reg_test ( name, "%d", &pokey.pc_pot[1], v_center );

    input_stick_h_axis ( 0, -50 );
    sprintf ( name, "POT Config P0 HPOT Low Clamp" );
    reg_test ( name, "%d", &pokey.pc_pot[0], h_low );
    input_stick_h_axis ( 0, 300 );
    sprintf ( name, "POT Config P0 HPOT High Clamp" );
    reg_test ( name, "%d", &pokey.pc_pot[0], h_high );
    input_stick_v_axis ( 0, -50 );
    sprintf ( name, "POT Config P0 VPOT Low Clamp" );
    reg_test ( name, "%d", &pokey.pc_pot[1], v_low );
    input_stick_v_axis ( 0, 300 );
    sprintf ( name, "POT Config P0 VPOT High Clamp" );
    reg_test ( name, "%d", &pokey.pc_pot[1], v_high );

    if ( p_config->general.machine == MACHINE_TYPE_5200 ) {
        int track_h_center = 90;
        int track_v_center = 91;
        int track_min = 5;
        int track_max = 17;

        p_config->machine.trackball_hpot_center = track_h_center;
        p_config->machine.trackball_vpot_center = track_v_center;
        p_config->machine.trackball_dpot_min = track_min;
        p_config->machine.trackball_dpot_max = track_max;
        p_config->input.game.players[0].type = CTRLR_TYPE_TRACKBALL;
        input_set_controls();

        sprintf ( name, "POT Config P0 Trackball HPOT Init" );
        reg_test ( name, "%d", &pokey.pc_pot[0], track_h_center );
        sprintf ( name, "POT Config P0 Trackball VPOT Init" );
        reg_test ( name, "%d", &pokey.pc_pot[1], track_v_center );
        sprintf ( name, "POT Config P0 Trackball DPOT Min Cache" );
        int_test ( name, "%d", g_stick_interface[0].trackball_dpot_min, track_min );
        sprintf ( name, "POT Config P0 Trackball DPOT Max Cache" );
        int_test ( name, "%d", g_stick_interface[0].trackball_dpot_max, track_max );

        pokey_set_controller_type ( 0, CTRL_BALL );
        gtia.pot_enable = 0;
        read_value = pokey_read_register ( POT0 );
        sprintf ( name, "POT Config P0 Trackball HPOT Disabled Read" );
        reg_test ( name, "%d", &read_value, track_h_center );
        read_value = pokey_read_register ( POT1 );
        sprintf ( name, "POT Config P0 Trackball VPOT Disabled Read" );
        reg_test ( name, "%d", &read_value, track_v_center );
        pokey_set_controller_type ( 0, CTRL_STICK );
        read_value = pokey_read_register ( POT0 );
        sprintf ( name, "POT Config P0 Stick HPOT Disabled Read" );
        reg_test ( name, "%d", &read_value, POT_OPEN_VALUE );
        read_value = pokey_read_register ( POT1 );
        sprintf ( name, "POT Config P0 Stick VPOT Disabled Read" );
        reg_test ( name, "%d", &read_value, POT_OPEN_VALUE );
        gtia.pot_enable = saved_pot_enable;
        pokey_set_controller_type ( 0, saved_controller_type );

        input_key_down_stick ( NULL, 0, DIR_LEFT, 0 );
        sprintf ( name, "POT Config P0 Trackball HPOT Left" );
        reg_test ( name, "%d", &pokey.pc_pot[0], track_h_center - track_max );
        input_key_up_stick ( NULL, 0, DIR_LEFT, 0 );
        sprintf ( name, "POT Config P0 Trackball HPOT Recenter" );
        reg_test ( name, "%d", &pokey.pc_pot[0], track_h_center );
        input_key_down_stick ( NULL, 0, DIR_DOWN, 0 );
        sprintf ( name, "POT Config P0 Trackball VPOT Down" );
        reg_test ( name, "%d", &pokey.pc_pot[1], track_v_center + track_max );
        input_key_up_stick ( NULL, 0, DIR_DOWN, 0 );
    }

    p_config->machine = saved_machine;
    p_config->input.game.players[0] = saved_p0;
    gtia.pot_enable = saved_pot_enable;
    pokey_set_controller_type ( 0, saved_controller_type );
    input_set_controls();

} /* end test_pokey_pot_config_values */

/*
 * test_key_input - test keyboard used as input
 */
void test_key_input ( void ) {

    int i, j, pot, b800, key, value, max_control;
    int saved_type[MAX_CONTROLLER];
    char name[257], tmp_string[257];
    t_config *p_config = config_get_global_cfg();

    if ( p_config->general.machine == MACHINE_TYPE_5200 ) {
        max_control = 4;
        b800 = 0;
    }
    else {
        max_control = 2;
        b800 = 1;
    }

    for ( i = 0; i < MAX_CONTROLLER; ++i )
        saved_type[i] = p_config->input.game.players[i].type;

    for ( i = 0; i < 2; ++i )
        p_config->input.game.players[i].type = CTRLR_TYPE_JOYSTICK;
    input_set_controls();

    //for ( i = 0; i < MAX_CONTROLLER; ++i ) {
    for ( i = 0; i < 2; ++i ) {

        /*
         * Test Up
         */
        sprintf ( name, "Key P%d Up", i );
        pot = i<<1 | DIR_UP>>1;
        switch ( i ) {
            case 0: key = SDLK_UP; value = 0xfe; break;
            case 1: key = SDLK_W; value = 0xef; break;
        }
        key_test ( SDL_KEYDOWN, 0, key, name, b800 ? "0x%02x" : "%d",
                b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                b800 ? value : test_pot_low ( i, TEST_AXIS_V, 0 ) );

        key_test ( SDL_KEYUP, 0, key, name, b800 ? "0x%02x" : "%d",
                b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                b800 ? 0xff : test_pot_center ( i, TEST_AXIS_V, 0 ) );

        /*
         * Test Down
         */
        sprintf ( name, "Key P%d Down", i );
        pot = i<<1 | DIR_DOWN>>1;
        switch ( i ) {
            case 0: key = SDLK_DOWN; value = 0xfd; break;
            case 1: key = SDLK_S; value = 0xdf; break;
        }
        key_test ( SDL_KEYDOWN, 0, key, name, b800 ? "0x%02x" : "%d",
                b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                b800 ? value : test_pot_high ( i, TEST_AXIS_V, 0 ) );

        key_test ( SDL_KEYUP, 0, key, name, b800 ? "0x%02x" : "%d",
                b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                b800 ? 0xff : test_pot_center ( i, TEST_AXIS_V, 0 ) );

        /*
         * Test Left
         */
        sprintf ( name, "Key P%d Left", i );
        pot = i<<1 | DIR_LEFT>>1;
        switch ( i ) {
            case 0: key = SDLK_LEFT; value = 0xfb; break;
            case 1: key = SDLK_A; value = 0xbf; break;
        }
        key_test ( SDL_KEYDOWN, 0, key, name, b800 ? "0x%02x" : "%d",
                b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                b800 ? value : test_pot_low ( i, TEST_AXIS_H, 0 ) );

        key_test ( SDL_KEYUP, 0, key, name, b800 ? "0x%02x" : "%d",
                b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                b800 ? 0xff : test_pot_center ( i, TEST_AXIS_H, 0 ) );

        /*
         * Test Right
         */
        sprintf ( name, "Key P%d Right", i );
        pot = i<<1 | DIR_RIGHT>>1;
        switch ( i ) {
            case 0: key = SDLK_RIGHT; value = 0xf7; break;
            case 1: key = SDLK_D; value = 0x7f; break;
        }
        key_test ( SDL_KEYDOWN, 0, key, name, b800 ? "0x%02x" : "%d",
                b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                b800 ? value : test_pot_high ( i, TEST_AXIS_H, 0 ) );

        key_test ( SDL_KEYUP, 0, key, name, b800 ? "0x%02x" : "%d",
                b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                b800 ? 0xff : test_pot_center ( i, TEST_AXIS_H, 0 ) );

        /*
         * Test Buttons and keypad
         */
        sprintf ( name, "Key P%d Bottom Button", i );
        switch ( i ) {
            case 0: key = SDLK_RETURN; break;
            case 1: key = SDLK_LSHIFT; break;
        }
        key_test ( SDL_KEYDOWN, 0, key, name, "%d",
                &gtia.trig[i], 0 );
        key_test ( SDL_KEYUP, 0, key, name, "%d",
                &gtia.trig[i], 1 );

        if ( p_config->general.machine == MACHINE_TYPE_5200 ) {
            sprintf ( name, "Key P%d Top Button", i );
            switch ( i ) {
                case 0: key = SDLK_RSHIFT; break;
                case 1: key = SDLK_LCTRL; break;
            }
            key_test ( SDL_KEYDOWN, 0, key, name, "%d",
                    &pokey.shift_pressed[i], 1 );
            key_test ( SDL_KEYUP, 0, key, name, "%d",
                    &pokey.shift_pressed[i], 0 );

            for ( j = 1; j < MAX_KEYPAD_KEYS; ++j ) {
                sprintf ( name, "Key P%d %s", i,
                        input_get_keypad_string(j, tmp_string) );

                switch ( (j<<1 | 1) ) {
                    case KEY_START: key = (i==0 ? SDLK_F10 : SDLK_F3); break;
                    case KEY_PAUSE: key = (i==0 ? SDLK_F11 : SDLK_F4); break;
                    case KEY_RESET: key = (i==0 ? SDLK_F12 : SDLK_F5); break;
                    case KEY_STAR:  key = (i==0 ? SDLK_KP_MULTIPLY : SDLK_Q); break;
                    case KEY_POUND: key = (i==0 ? SDLK_KP_DIVIDE : SDLK_TAB); break;
                    case KEY_0:     key = (i==0 ? SDLK_0 : SDLK_KP_0); break;
                    case KEY_1:     key = (i==0 ? SDLK_1 : SDLK_KP_1); break;
                    case KEY_2:     key = (i==0 ? SDLK_2 : SDLK_KP_2); break;
                    case KEY_3:     key = (i==0 ? SDLK_3 : SDLK_KP_3); break;
                    case KEY_4:     key = (i==0 ? SDLK_4 : SDLK_KP_4); break;
                    case KEY_5:     key = (i==0 ? SDLK_5 : SDLK_KP_5); break;
                    case KEY_6:     key = (i==0 ? SDLK_6 : SDLK_KP_6); break;
                    case KEY_7:     key = (i==0 ? SDLK_7 : SDLK_KP_7); break;
                    case KEY_8:     key = (i==0 ? SDLK_8 : SDLK_KP_8); break;
                    case KEY_9:     key = (i==0 ? SDLK_9 : SDLK_KP_9); break;
                }

                key_test ( SDL_KEYDOWN, 0, key, name, "%d",
                        &pokey.key_pressed[i][j], 1 );
                key_test ( SDL_KEYUP, 0, key, name, "%d",
                        &pokey.key_pressed[i][j], 0 );

            } /* end for keypad key */

        } /* end if 5200 */

    } /* for each controller */

    /*
     * 8-bit Keyboard
     */
    if ( p_config->general.machine != MACHINE_TYPE_5200 ) {

        for ( j = 1; j < MAX_KEYS; ++j ) {
            sprintf ( name, "Key %s", input_get_key_string(j, tmp_string) );

            if ( strlen(tmp_string) == 0 )
                continue;

            switch ( (j ) ) {
                case A800_KEY_L:      key = SDLK_L; break;
                case A800_KEY_J:      key = SDLK_J; break;
                case A800_KEY_SEMI:   key = SDLK_SEMICOLON; break;
                case A800_KEY_K:      key = SDLK_K; break;
                case A800_KEY_PLUS:   key = SDLK_KP_PLUS; break;
                case A800_KEY_STAR:   key = SDLK_KP_MULTIPLY; break;
                case A800_KEY_O:      key = SDLK_O; break;
                case A800_KEY_P:      key = SDLK_P; break;
                case A800_KEY_U:      key = SDLK_U; break;
                case A800_KEY_RETURN: key = SDLK_RETURN; break;
                case A800_KEY_I:      key = SDLK_I; break;
                case A800_KEY_MINUS:  key = SDLK_MINUS; break;
                case A800_KEY_EQUALS: key = SDLK_EQUALS; break;
                case A800_KEY_V:      key = SDLK_V; break;
                case A800_KEY_HELP:   key = SDLK_F1; break;
                case A800_KEY_C:      key = SDLK_C; break;
                case A800_KEY_B:      key = SDLK_B; break;
                case A800_KEY_X:      key = SDLK_X; break;
                case A800_KEY_Z:      key = SDLK_Z; break;
                case A800_KEY_4:      key = SDLK_4; break;
                case A800_KEY_3:      key = SDLK_3; break;
                case A800_KEY_6:      key = SDLK_6; break;
                case A800_KEY_ESC:    key = SDLK_ESCAPE; break;
                case A800_KEY_5:      key = SDLK_5; break;
                case A800_KEY_2:      key = SDLK_2; break;
                case A800_KEY_1:      key = SDLK_1; break;
                case A800_KEY_COMMA:  key = SDLK_COMMA; break;
                case A800_KEY_SPACE:  key = SDLK_SPACE; break;
                case A800_KEY_PERIOD: key = SDLK_PERIOD; break;
                case A800_KEY_N:      key = SDLK_N; break;
                case A800_KEY_M:      key = SDLK_M; break;
                case A800_KEY_SLASH:  key = SDLK_SLASH; break;
                case A800_KEY_FUJI:   key = SDLK_LGUI; break;
                case A800_KEY_R:      key = SDLK_R; break;
                case A800_KEY_E:      key = SDLK_E; break;
                case A800_KEY_Y:      key = SDLK_Y; break;
                case A800_KEY_TAB:    key = SDLK_TAB; break;
                case A800_KEY_T:      key = SDLK_T; break;
                case A800_KEY_W:      key = SDLK_W; break;
                case A800_KEY_Q:      key = SDLK_Q; break;
                case A800_KEY_9:      key = SDLK_9; break;
                case A800_KEY_0:      key = SDLK_0; break;
                case A800_KEY_7:      key = SDLK_7; break;
                case A800_KEY_BKSP:   key = SDLK_BACKSPACE; break;
                case A800_KEY_8:      key = SDLK_8; break;
                case A800_KEY_LESST:  key = SDLK_LEFTBRACKET; break;
                case A800_KEY_MORET:  key = SDLK_RIGHTBRACKET; break;
                case A800_KEY_F:      key = SDLK_F; break;
                case A800_KEY_H:      key = SDLK_H; break;
                case A800_KEY_D:      key = SDLK_D; break;
                case A800_KEY_CAPS:   key = SDLK_CAPSLOCK; break;
                case A800_KEY_G:      key = SDLK_G; break;
                case A800_KEY_S:      key = SDLK_S; break;
                case A800_KEY_A:      key = SDLK_A; break;
            }

            key_test ( SDL_KEYDOWN, 0, key, name, "%d",
                    &pokey.key_pressed[0][j], 1 );
            key_test ( SDL_KEYUP, 0, key, name, "%d",
                    &pokey.key_pressed[0][j], 0 );

        } /* end for keypad key */

    } /* end if not 5200 */

    for ( i = 0; i < MAX_CONTROLLER; ++i )
        p_config->input.game.players[i].type = saved_type[i];
    input_set_controls();

} /* end test_key_input */

/*
 * test_joystick_input - test joystick used as input
 */
void test_joystick_input ( void ) {

    int i, j, pot, button, b800, max_con, value, combo;
    char name[257], tmp_string[257];
    t_config *p_config = config_get_global_cfg();

    if ( p_config->general.machine == MACHINE_TYPE_5200 ) {
        b800 = 0;
        max_con = 4;
    }
    else {
        b800 = 1;
        max_con = 2;
    }

    for ( i = 0; i < max_con; ++i ) {

        for ( j = 0; j < 2; ++j ) {
            if ( j == 1 && b800 )
                break;

            /*
             * Force the controller type so the test does not depend on the
             * config default (players 2/3 are "None" in the test conf).
             */
            if ( j == 1 )
                p_config->input.game.players[i].type = CTRLR_TYPE_TRACKBALL;
            else
                p_config->input.game.players[i].type = CTRLR_TYPE_JOYSTICK;
            input_set_controls();

            /*
             * Test Up
             */
            sprintf ( name, "%s P%d Up", j==0 ? "Joy" : "Trackball", i );
            pot = i<<1 | DIR_UP>>1;
            switch ( i ) {
                case 0: value = 0xfe; break;
                case 1: value = 0xef; break;
            }
            axis_test ( SDL_JOYAXISMOTION, i, 1, -20000, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? value : test_pot_lut ( i, TEST_AXIS_V, -20000 ) );

            /*
             * Test Down
             */
            sprintf ( name, "%s P%d Down", j==0 ? "Joy" : "Trackball", i );
            pot = i<<1 | DIR_DOWN>>1;
            switch ( i ) {
                case 0: value = 0xfd; break;
                case 1: value = 0xdf; break;
            }
            axis_test ( SDL_JOYAXISMOTION, i, 1, 28000, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? value : test_pot_lut ( i, TEST_AXIS_V, 28000 ) );

            axis_test ( SDL_JOYAXISMOTION, i, 1, 0, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? 0xff : test_pot_lut ( i, TEST_AXIS_V, 0 ) );

            /*
             * Test Left
             */
            sprintf ( name, "%s P%d Left", j==0 ? "Joy" : "Trackball", i );
            pot = i<<1 | DIR_LEFT>>1;
            switch ( i ) {
                case 0: value = 0xfb; break;
                case 1: value = 0xbf; break;
            }
            axis_test ( SDL_JOYAXISMOTION, i, 0, -17000, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? value : test_pot_lut ( i, TEST_AXIS_H, -17000 ) );

            /*
             * Test Right
             */
            sprintf ( name, "%s P%d Right", j==0 ? "Joy" : "Trackball", i );
            pot = i<<1 | DIR_RIGHT>>1;
            switch ( i ) {
                case 0: value = 0xf7; break;
                case 1: value = 0x7f; break;
            }
            axis_test ( SDL_JOYAXISMOTION, i, 0, 31000, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? value : test_pot_lut ( i, TEST_AXIS_H, 31000 ) );
            axis_test ( SDL_JOYAXISMOTION, i, 0, 0, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? 0xff : test_pot_lut ( i, TEST_AXIS_H, 0 ) );

            /*
             * DPAD
             */
            sprintf ( name, "Dpad P%d Up/Right", i );
            pot = i<<1 | DIR_UP>>1;
            switch ( i ) {
                case 0: value = 0xf6; break;
                case 1: value = 0x6f; break;
            }
            dpad_test ( i, SDL_HAT_UP | SDL_HAT_RIGHT, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? value : test_pot_low ( i, TEST_AXIS_V, j == 1 ) );
            dpad_test ( i, 0, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? 0xff : test_pot_center ( i, TEST_AXIS_V, j == 1 ) );

            sprintf ( name, "Dpad P%d Down/Left", i );
            pot = i<<1 | DIR_LEFT>>1;
            switch ( i ) {
                case 0: value = 0xf9; break;
                case 1: value = 0x9f; break;
            }
            dpad_test ( i, SDL_HAT_DOWN | SDL_HAT_LEFT, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? value : test_pot_low ( i, TEST_AXIS_H, j == 1 ) );
            dpad_test ( i, 0, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? 0xff : test_pot_center ( i, TEST_AXIS_H, j == 1 ) );

            if ( j ==1 ) {
                p_config->input.game.players[i].type = CTRLR_TYPE_JOYSTICK;
                input_set_controls();
            }

        } /* end for joystick and trackball */

        /*
         * Test Buttons and keypad
         */
        sprintf ( name, "Joy P%d Bottom Button", i );
        button_test ( SDL_JOYBUTTONDOWN, i, 0, name, "%d",
                &gtia.trig[i], 0);
        button_test ( SDL_JOYBUTTONUP, i, 0, name, "%d",
                &gtia.trig[i], 1);

        if ( p_config->general.machine == MACHINE_TYPE_5200 ) {
            sprintf ( name, "Joy P%d Top Button", i );
            button_test ( SDL_JOYBUTTONDOWN, i, 1, name, "%d",
                    &pokey.shift_pressed[i], 1);
            button_test ( SDL_JOYBUTTONUP, i, 1, name, "%d",
                    &pokey.shift_pressed[i], 0);

            for ( j = 1; j < MAX_KEYPAD_KEYS; ++j ) {
                sprintf ( name, "Joy P%d %s", i,
                        input_get_keypad_string(j, tmp_string) );

                switch ( (j<<1 | 1) ) {
                    case KEY_START: button = 6; combo=0; value = 0; break;
                    case KEY_PAUSE: button = 9; combo=0; value = 0; break;
                    case KEY_RESET: button = 10; combo=0; value = 0; break;
                    case KEY_STAR:  button = 2; combo=0; value = 0; break;
                    case KEY_POUND: button = 3; combo=0; value = 0; break;
                    case KEY_0:     button = 3; combo=4; value = 20000; break;
                    case KEY_1:     button = 2; combo=4; value = -20000; break;
                    case KEY_2:     button = 3; combo=4; value = -20000; break;
                    case KEY_3:     button = 0; combo=4; value = -20000; break;
                    case KEY_4:     button = 1; combo=4; value = -20000; break;
                    case KEY_5:     button = 2; combo=3; value = 20000; break;
                    case KEY_6:     button = 3; combo=3; value = 20000; break;
                    case KEY_7:     button = 0; combo=3; value = 20000; break;
                    case KEY_8:     button = 1; combo=3; value = 20000; break;
                    case KEY_9:     button = 2; combo=4; value = 20000; break;
                }


                if ( combo ) {
                    event.type = SDL_JOYAXISMOTION;
                    event.jaxis.which = i;
                    event.jaxis.axis = combo;
                    event.jaxis.value = value;
                    SDL_PushEvent(&event);
                    pc_poll_input_events ( &event );
                }

                button_test ( SDL_JOYBUTTONDOWN, i, button, name, "%d",
                        &pokey.key_pressed[i][j], 1);
                button_test ( SDL_JOYBUTTONUP, i, button, name, "%d",
                        &pokey.key_pressed[i][j], 0);

                if ( combo ) {
                    event.type = SDL_JOYAXISMOTION;
                    event.jaxis.which = i;
                    event.jaxis.axis = combo;
                    event.jaxis.value = 0;
                    SDL_PushEvent(&event);
                    pc_poll_input_events ( &event );
                }

            } /* end for keypad key */

        } /* end if 5200 */

        /*
         * Do paddles
         */
        if ( p_config->general.machine != MACHINE_TYPE_5200 ) {

            p_config->input.game.players[0].type = CTRLR_TYPE_PADDLES;
            p_config->input.game.players[1].type = CTRLR_TYPE_PADDLES;
            p_config->input.game.players[2].type = CTRLR_TYPE_PADDLES;
            p_config->input.game.players[3].type = CTRLR_TYPE_PADDLES;
            input_set_controls();

            sprintf ( name, "Paddle 1 P%d Left", i );
            axis_test ( SDL_JOYAXISMOTION, i*2, 0, -17000, name, "%d",
                    &pokey.pc_pot[i*2],
                    test_pot_lut ( i, TEST_AXIS_H, -17000 ) );

            sprintf ( name, "Paddle 1 P%d Right", i );
            axis_test ( SDL_JOYAXISMOTION, i*2, 0, 19000, name, "%d",
                    &pokey.pc_pot[i*2],
                    test_pot_lut ( i, TEST_AXIS_H, 19000 ) );

            sprintf ( name, "Paddle 1 P%d Button", i );
            button_test ( SDL_JOYBUTTONDOWN, i*2, 0, name, "0x%02x",
                    &pia.input[i>>1], i==1 ? 0xbf : 0xfb);
            button_test ( SDL_JOYBUTTONUP, i*2, 0, name, "0x%02x",
                    &pia.input[i>>1], 0xff);

            sprintf ( name, "Paddle 2 P%d Left", i );
            axis_test ( SDL_JOYAXISMOTION, i*2+1, 0, -17000, name, "%d",
                    &pokey.pc_pot[i*2+1],
                    test_pot_lut ( i, TEST_AXIS_V, -17000 ) );

            sprintf ( name, "Paddle 2 P%d Right", i );
            axis_test ( SDL_JOYAXISMOTION, i*2+1, 0, 19000, name, "%d",
                    &pokey.pc_pot[i*2+1],
                    test_pot_lut ( i, TEST_AXIS_V, 19000 ) );

            sprintf ( name, "Paddle 2 P%d Button", i );
            button_test ( SDL_JOYBUTTONDOWN, i*2+1, 0, name, "0x%02x",
                    &pia.input[i>>1], i==1 ? 0x7f : 0xf7);
            button_test ( SDL_JOYBUTTONUP, i*2+1, 0, name, "0x%02x",
                    &pia.input[i>>1], 0xff);

            sprintf ( name, "Paddle P%d Both Buttons P1 Down", i );
            button_test ( SDL_JOYBUTTONDOWN, i*2, 0, name, "0x%02x",
                    &pia.input[i>>1], i==1 ? 0xbf : 0xfb);
            sprintf ( name, "Paddle P%d Both Buttons P2 Down", i );
            button_test ( SDL_JOYBUTTONDOWN, i*2+1, 0, name, "0x%02x",
                    &pia.input[i>>1], i==1 ? 0x3f : 0xf3);
            sprintf ( name, "Paddle P%d Both Buttons P1 Up", i );
            button_test ( SDL_JOYBUTTONUP, i*2, 0, name, "0x%02x",
                    &pia.input[i>>1], i==1 ? 0x7f : 0xf7);
            sprintf ( name, "Paddle P%d Both Buttons P2 Up", i );
            button_test ( SDL_JOYBUTTONUP, i*2+1, 0, name, "0x%02x",
                    &pia.input[i>>1], 0xff);

            p_config->input.game.players[0].type = CTRLR_TYPE_JOYSTICK;
            p_config->input.game.players[1].type = CTRLR_TYPE_JOYSTICK;
            p_config->input.game.players[2].type = CTRLR_TYPE_NONE;
            p_config->input.game.players[3].type = CTRLR_TYPE_NONE;
            input_set_controls();

        } /* end if not 5200 do paddles */

    } /* for each controller */

} /* end test_joystick_input */

/*
 * test_controller - test game controller used as input
 */
void test_controller_input ( void ) {

    int i, j, pot, button, b800, value, combo;
    char name[257], tmp_string[257];
    t_config *p_config = config_get_global_cfg();

    if ( p_config->general.machine == MACHINE_TYPE_5200 ) {
        b800 = 0;
    }
    else {
        b800 = 1;
    }

    for ( i = 0; i < 2; ++i ) {
    //for ( i = 0; i < MAX_CONTROLLER; ++i ) {
    //
        for ( j = 0; j < 2; ++j ) {
            if ( j == 1 && b800 )
                break;

            if ( j ==1 ) {
                p_config->input.game.players[i].type = CTRLR_TYPE_TRACKBALL;
                input_set_controls();
            }

            /*
             * Test Up
             */
            sprintf ( name, "%sController P%d Up", j==1 ? "Trackball" : "", i );
            pot = i<<1 | DIR_UP>>1;
            switch ( i ) {
                case 0: value = 0xfe; break;
                case 1: value = 0xef; break;
            }
            axis_test ( SDL_CONTROLLERAXISMOTION, i, 1, -20000, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? value : test_pot_lut ( i, TEST_AXIS_V, -20000 ) );

            /*
             * Test Down
             */
            sprintf ( name, "%sController P%d Down", j==1 ? "Trackball" : "", i );
            pot = i<<1 | DIR_DOWN>>1;
            switch ( i ) {
                case 0: value = 0xfd; break;
                case 1: value = 0xdf; break;
            }
            axis_test ( SDL_CONTROLLERAXISMOTION, i, 1, 28000, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? value : test_pot_lut ( i, TEST_AXIS_V, 28000 ) );

            axis_test ( SDL_CONTROLLERAXISMOTION, i, 1, 0, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? 0xff : test_pot_lut ( i, TEST_AXIS_V, 0 ) );

            /*
             * Test Left
             */
            sprintf ( name, "%sController P%d Left", j==1 ? "Trackball" : "", i );
            pot = i<<1 | DIR_LEFT>>1;
            switch ( i ) {
                case 0: value = 0xfb; break;
                case 1: value = 0xbf; break;
            }
            axis_test ( SDL_CONTROLLERAXISMOTION, i, 0, -17000, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? value : test_pot_lut ( i, TEST_AXIS_H, -17000 ) );

            /*
             * Test Right
             */
            sprintf ( name, "%sController P%d Right", j==1 ? "Trackball" : "", i );
            pot = i<<1 | DIR_RIGHT>>1;
            switch ( i ) {
                case 0: value = 0xf7; break;
                case 1: value = 0x7f; break;
            }
            axis_test ( SDL_CONTROLLERAXISMOTION, i, 0, 31000, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? value : test_pot_lut ( i, TEST_AXIS_H, 31000 ) );
            axis_test ( SDL_CONTROLLERAXISMOTION, i, 0, 0, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? 0xff : test_pot_lut ( i, TEST_AXIS_H, 0 ) );
            /*
             * DPAD
             */
            sprintf ( name, "C_Dpad P%d Up", i );
            pot = i<<1 | DIR_UP>>1;
            switch ( i ) {
                case 0: value = 0xfe; break;
                case 1: value = 0xef; break;
            }
            button_test ( SDL_CONTROLLERBUTTONDOWN, i, SDL_CONTROLLER_BUTTON_DPAD_UP, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? value : test_pot_low ( i, TEST_AXIS_V, j == 1 ) );
            button_test ( SDL_CONTROLLERBUTTONUP, i, SDL_CONTROLLER_BUTTON_DPAD_UP, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? 0xff : test_pot_center ( i, TEST_AXIS_V, j == 1 ) );

            sprintf ( name, "C_Dpad P%d Down", i );
            pot = i<<1 | DIR_DOWN>>1;
            switch ( i ) {
                case 0: value = 0xfd; break;
                case 1: value = 0xdf; break;
            }
            button_test ( SDL_CONTROLLERBUTTONDOWN, i, SDL_CONTROLLER_BUTTON_DPAD_DOWN, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? value : test_pot_high ( i, TEST_AXIS_V, j == 1 ) );
            button_test ( SDL_CONTROLLERBUTTONUP, i, SDL_CONTROLLER_BUTTON_DPAD_DOWN, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? 0xff : test_pot_center ( i, TEST_AXIS_V, j == 1 ) );

            sprintf ( name, "C_Dpad P%d Left", i );
            pot = i<<1 | DIR_LEFT>>1;
            switch ( i ) {
                case 0: value = 0xfb; break;
                case 1: value = 0xbf; break;
            }
            button_test ( SDL_CONTROLLERBUTTONDOWN, i, SDL_CONTROLLER_BUTTON_DPAD_LEFT, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? value : test_pot_low ( i, TEST_AXIS_H, j == 1 ) );
            button_test ( SDL_CONTROLLERBUTTONUP, i, SDL_CONTROLLER_BUTTON_DPAD_LEFT, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? 0xff : test_pot_center ( i, TEST_AXIS_H, j == 1 ) );

            sprintf ( name, "C_Dpad P%d Right", i );
            pot = i<<1 | DIR_LEFT>>1;
            switch ( i ) {
                case 0: value = 0xf7; break;
                case 1: value = 0x7f; break;
            }
            button_test ( SDL_CONTROLLERBUTTONDOWN, i, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? value : test_pot_high ( i, TEST_AXIS_H, j == 1 ) );
            button_test ( SDL_CONTROLLERBUTTONUP, i, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, name, b800 ? "0x%02x" : "%d",
                    b800 ? &pia.input[i>>1] : &pokey.pc_pot[pot],
                    b800 ? 0xff : test_pot_center ( i, TEST_AXIS_H, j == 1 ) );

            if ( j ==1 ) {
                p_config->input.game.players[i].type = CTRLR_TYPE_JOYSTICK;
                input_set_controls();
            }

        } /* end for joystick and trackball */
        /*
         * Test Buttons and keypad
         */
        sprintf ( name, "Controller P%d Bottom Button", i );
        button_test ( SDL_CONTROLLERBUTTONDOWN, i, 0, name, "%d",
                &gtia.trig[i], 0);
        button_test ( SDL_CONTROLLERBUTTONUP, i, 0, name, "%d",
                &gtia.trig[i], 1);

        if ( p_config->general.machine == MACHINE_TYPE_5200 ) {
            sprintf ( name, "Controller P%d Top Button", i );
            button_test ( SDL_CONTROLLERBUTTONDOWN, i, 1, name, "%d",
                    &pokey.shift_pressed[i], 1);
            button_test ( SDL_CONTROLLERBUTTONUP, i, 1, name, "%d",
                    &pokey.shift_pressed[i], 0);

            for ( j = 1; j < MAX_KEYPAD_KEYS; ++j ) {
                sprintf ( name, "Controller P%d %s", i,
                        input_get_keypad_string(j, tmp_string) );

                switch ( (j<<1 | 1) ) {
                    case KEY_START: button = SDL_CONTROLLER_BUTTON_START;
                                    combo=0; value = 0; break;
                    case KEY_PAUSE: button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
                                    combo=0; value = 0; break;
                    case KEY_RESET: button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
                                    combo=0; value = 0; break;
                    case KEY_STAR:  button = SDL_CONTROLLER_BUTTON_X;
                                    combo=0; value = 0; break;
                    case KEY_POUND: button = SDL_CONTROLLER_BUTTON_Y;
                                    combo=0; value = 0; break;
                    case KEY_0:     button = SDL_CONTROLLER_BUTTON_Y;
                                    combo=SDL_CONTROLLER_AXIS_RIGHTY; value = 20000; break;
                    case KEY_1:     button = SDL_CONTROLLER_BUTTON_A;
                                    combo=SDL_CONTROLLER_AXIS_RIGHTY; value = -20000; break;
                    case KEY_2:     button = SDL_CONTROLLER_BUTTON_B;
                                    combo=SDL_CONTROLLER_AXIS_RIGHTY; value = -20000; break;
                    case KEY_3:     button = SDL_CONTROLLER_BUTTON_X;
                                    combo=SDL_CONTROLLER_AXIS_RIGHTY; value = -20000; break;
                    case KEY_4:     button = SDL_CONTROLLER_BUTTON_Y;
                                    combo=SDL_CONTROLLER_AXIS_RIGHTY; value = -20000; break;
                    case KEY_5:     button = SDL_CONTROLLER_BUTTON_A;
                                    combo=SDL_CONTROLLER_AXIS_RIGHTX; value = 20000; break;
                    case KEY_6:     button = SDL_CONTROLLER_BUTTON_B;
                                    combo=SDL_CONTROLLER_AXIS_RIGHTX; value = 20000; break;
                    case KEY_7:     button = SDL_CONTROLLER_BUTTON_X;
                                    combo=SDL_CONTROLLER_AXIS_RIGHTX; value = 20000; break;
                    case KEY_8:     button = SDL_CONTROLLER_BUTTON_Y;
                                    combo=SDL_CONTROLLER_AXIS_RIGHTX; value = 20000; break;
                    case KEY_9:     button = SDL_CONTROLLER_BUTTON_X;
                                    combo=SDL_CONTROLLER_AXIS_RIGHTY; value = 20000; break;
                }


                if ( combo ) {
                    event.type = SDL_CONTROLLERAXISMOTION;
                    event.caxis.which = i;
                    event.caxis.axis = combo;
                    event.caxis.value = value;
                    SDL_PushEvent(&event);
                    pc_poll_input_events ( &event );
                }

                button_test ( SDL_CONTROLLERBUTTONDOWN, i, button, name, "%d",
                        &pokey.key_pressed[i][j], 1);
                button_test ( SDL_CONTROLLERBUTTONUP, i, button, name, "%d",
                        &pokey.key_pressed[i][j], 0);

                if ( combo ) {
                    event.type = SDL_CONTROLLERAXISMOTION;
                    event.caxis.which = i;
                    event.caxis.axis = combo;
                    event.caxis.value = 0;
                    SDL_PushEvent(&event);
                    pc_poll_input_events ( &event );
                }

            } /* end for keypad key */

        } /* end if 5200 */

        /*
         * Do paddles
         */
        if ( p_config->general.machine != MACHINE_TYPE_5200 ) {

        } /* end if not 5200 */

    } /* for each controller */

} /* end test_controller_input */

/*
 * test_mouse_input - test mouse used as input
 */
void test_mouse_input ( void ) {

    int i, j, pot, button, center, b800, max_con, value, combo;
    char name[257], tmp_string[257];
    t_config *p_config = config_get_global_cfg();

    if ( p_config->general.machine == MACHINE_TYPE_5200 ) {
        b800 = 0;
        center = test_pot_center ( 0, TEST_AXIS_H, 1 );
    }
    else {
        b800 = 1;
        center = test_pot_center ( 0, TEST_AXIS_H, 0 );
    }

    /*
     * Do Trackball for 5200
     */
    if ( p_config->general.machine == MACHINE_TYPE_5200 ) {
        t_player_input saved_p0 = p_config->input.game.players[0];
        t_input_common mouse_left_y, mouse_right_y;
        int base_x, base_y, expected;

        p_config->input.game.players[0].type = CTRLR_TYPE_TRACKBALL;
        p_config->input.game.players[0].mouse_speed = 3;
        input_set_controls();

        /*
         * Trackball model: DPOT = raw_frame_motion * speed * 0.5, then clamp
         * the magnitude to the configured DPOT envelope, [4, 36] by default
         * (the min applies because the default optimize is PADDLES).  speed ==
         * 3 here, so motion 3 -> 4.5 -> 4, motion 6 -> 9, big swipes cap at 36.
         */
        ball_test ( SDL_MOUSEMOTION, 0, 0, 3, 0, "Mouse Trackball P0 Right", "%d",
                &pokey.pc_pot[0], test_pot_center ( 0, TEST_AXIS_H, 1 ) + 4 );

        ball_test ( SDL_MOUSEMOTION, 0, 0, 0, -6, "Mouse Trackball P0 Up", "%d",
                &pokey.pc_pot[1], test_pot_center ( 0, TEST_AXIS_V, 1 ) - 9 );

        ball_test ( SDL_MOUSEMOTION, 0, 0, 800, 0, "Mouse Trackball P0 Right Cap", "%d",
                &pokey.pc_pot[0], test_pot_center ( 0, TEST_AXIS_H, 1 ) + 36 );

        ball_test ( SDL_MOUSEMOTION, 0, 0, 0, -800, "Mouse Trackball P0 Up Cap", "%d",
                &pokey.pc_pot[1], test_pot_center ( 0, TEST_AXIS_V, 1 ) - 36 );

        ball_test ( SDL_MOUSEMOTION, 0, 0, 0, 0, "Mouse Trackball P0 Zero Preserve", "%d",
                &pokey.pc_pot[0], test_pot_center ( 0, TEST_AXIS_H, 1 ) + 36 );

        /*
         * SDL3 mouse motion is float.  At speed 1, fourteen 0.5 reports sum to
         * raw 7.0 -> 7*1*0.5 = 3.5, which the PADDLES min-DPOT bumps to 4, one
         * per-frame trackball write.
         */
        p_config->input.game.players[0].mouse_speed = 1;
        input_set_controls();

        for ( i = 0; i < 14; ++i )
            push_ball_event ( SDL_MOUSEMOTION, 0, 0, 0.5f, 0.0f );
        input_flush_mouse_motion();
        sprintf ( name, "Mouse Trackball P0 Fractional Right Speed1" );
        reg_test ( name, "%d", &pokey.pc_pot[0],
                test_pot_center ( 0, TEST_AXIS_H, 1 ) + 4 );

        input_set_controls();
        for ( i = 0; i < 14; ++i )
            push_ball_event ( SDL_MOUSEMOTION, 0, 0, -0.5f, 0.0f );
        input_flush_mouse_motion();
        sprintf ( name, "Mouse Trackball P0 Fractional Left Speed1" );
        reg_test ( name, "%d", &pokey.pc_pot[0],
                test_pot_center ( 0, TEST_AXIS_H, 1 ) - 4 );

        /*
         * Two speed-3 reports in one frame combine (raw 8.0 -> 8*3*0.5 = 12)
         * before the center-relative position is written.
         */
        p_config->input.game.players[0].mouse_speed = 3;
        input_set_controls();
        push_ball_event ( SDL_MOUSEMOTION, 0, 0, 4.0f, 0.0f );
        push_ball_event ( SDL_MOUSEMOTION, 0, 0, 4.0f, 0.0f );
        input_flush_mouse_motion();
        sprintf ( name, "Mouse Trackball P0 Same Frame Aggregate" );
        reg_test ( name, "%d", &pokey.pc_pot[0],
                test_pot_center ( 0, TEST_AXIS_H, 1 ) + 12 );

        /*
         * Within-frame direction reversal cancels in the raw sum: +4 then -1 ->
         * raw 3.0 -> 3*3*0.5 = 4.5 -> 4.  No remainder/reversal bookkeeping.
         */
        input_set_controls();
        push_ball_event ( SDL_MOUSEMOTION, 0, 0, 4.0f, 0.0f );
        push_ball_event ( SDL_MOUSEMOTION, 0, 0, -1.0f, 0.0f );
        input_flush_mouse_motion();
        sprintf ( name, "Mouse Trackball P0 Reversal Cancel" );
        reg_test ( name, "%d", &pokey.pc_pot[0],
                test_pot_center ( 0, TEST_AXIS_H, 1 ) + 4 );

        for ( i = 0; i < 4; ++i )
            g_pc_input.frame_x_function[0] ( NULL, 0, 0 );
        sprintf ( name, "Mouse Trackball P0 Recenter" );
        reg_test ( name, "%d", &pokey.pc_pot[0],
                test_pot_center ( 0, TEST_AXIS_H, 1 ) );

        button_test ( SDL_MOUSEBUTTONDOWN, 0, 1, "Mouse Bottom Button P0", "%d",
                &gtia.trig[0], 0 );
        button_test ( SDL_MOUSEBUTTONUP, 0, 1, "Mouse Bottom Button P0", "%d",
                &gtia.trig[0], 1 );

        button_test ( SDL_MOUSEBUTTONDOWN, 0, 3, "Mouse Top Button P0", "%d",
                &pokey.shift_pressed[0], 1 );
        button_test ( SDL_MOUSEBUTTONUP, 0, 3, "Mouse Top Button P0", "%d",
                &pokey.shift_pressed[0], 0 );

        p_config->input.game.players[0] = saved_p0;
        p_config->input.game.players[0].type = CTRLR_TYPE_JOYSTICK;
        p_config->input.game.players[0].mouse_enable = 1;
        p_config->input.game.players[0].mouse_speed = 3;
        p_config->input.game.players[0].mouse_optimize = MOUSE_OPTIMIZE_PADDLES;

        set_mouse_axis_input ( &p_config->input.game.players[0].stick[DIR_LEFT],
                0, DIR_MINUS );
        set_mouse_axis_input ( &p_config->input.game.players[0].stick[DIR_RIGHT],
                0, DIR_PLUS );
        set_mouse_axis_input ( &p_config->input.game.players[0].stick[DIR_UP],
                1, DIR_MINUS );
        set_mouse_axis_input ( &p_config->input.game.players[0].stick[DIR_DOWN],
                1, DIR_PLUS );
        input_set_controls();
        input_flush_mouse_motion();

        base_x = g_stick_interface[0].axis_value[0];
        expected = base_x - (int)(20.0f * g_pc_input.mouse.speed * 0.15f);
        ball_test ( SDL_MOUSEMOTION, 0, 0, -20, 0,
                "Mouse Joystick P0 Standard Left", "%d",
                &pokey.pc_pot[0], expected );

        input_set_controls();
        input_flush_mouse_motion();
        base_y = g_stick_interface[0].axis_value[1];
        expected = base_y - (int)(20.0f * g_pc_input.mouse.speed * 0.15f);
        ball_test ( SDL_MOUSEMOTION, 0, 0, 0, -20,
                "Mouse Joystick P0 Standard Up", "%d",
                &pokey.pc_pot[1], expected );

        p_config->input.game.players[0] = saved_p0;
        p_config->input.game.players[0].type = CTRLR_TYPE_JOYSTICK;
        p_config->input.game.players[0].mouse_enable = 1;
        p_config->input.game.players[0].mouse_speed = 3;
        p_config->input.game.players[0].mouse_optimize = MOUSE_OPTIMIZE_PADDLES;
        set_mouse_axis_input ( &p_config->input.game.players[0].stick[DIR_LEFT],
                1, DIR_MINUS );
        set_mouse_axis_input ( &p_config->input.game.players[0].stick[DIR_RIGHT],
                1, DIR_PLUS );
        input_set_controls();
        input_flush_mouse_motion();

        base_x = g_stick_interface[0].axis_value[0];
        base_y = pokey.pc_pot[1];
        expected = base_x - (int)(20.0f * g_pc_input.mouse.speed * 0.15f);
        ball_test ( SDL_MOUSEMOTION, 0, 0, 0, -20,
                "Mouse Joystick P0 Up Drives Left", "%d",
                &pokey.pc_pot[0], expected );
        sprintf ( name, "Mouse Joystick P0 Up Leaves Vertical" );
        reg_test ( name, "%d", &pokey.pc_pot[1], base_y );

        input_set_controls();
        input_flush_mouse_motion();
        base_x = g_stick_interface[0].axis_value[0];
        expected = base_x + (int)(20.0f * g_pc_input.mouse.speed * 0.15f);
        ball_test ( SDL_MOUSEMOTION, 0, 0, 0, 20,
                "Mouse Joystick P0 Down Drives Right", "%d",
                &pokey.pc_pot[0], expected );

        p_config->input.game.players[0] = saved_p0;
        p_config->input.game.players[0].type = CTRLR_TYPE_JOYSTICK;
        p_config->input.game.players[0].mouse_enable = 1;
        p_config->input.game.players[0].mouse_speed = 3;
        p_config->input.game.players[0].mouse_optimize = MOUSE_OPTIMIZE_PADDLES;
        set_mouse_axis_input ( &p_config->input.game.players[0].stick[DIR_LEFT],
                0, DIR_MINUS );
        set_mouse_axis_input ( &mouse_left_y, 1, DIR_MINUS );
        p_config->input.game.players[0].stick[DIR_LEFT].next = &mouse_left_y;
        set_mouse_axis_input ( &p_config->input.game.players[0].stick[DIR_RIGHT],
                0, DIR_PLUS );
        set_mouse_axis_input ( &mouse_right_y, 1, DIR_PLUS );
        p_config->input.game.players[0].stick[DIR_RIGHT].next = &mouse_right_y;
        input_set_controls();
        input_flush_mouse_motion();

        base_x = g_stick_interface[0].axis_value[0];
        base_y = pokey.pc_pot[1];
        push_ball_event ( SDL_MOUSEMOTION, 0, 0, -10.0f, -10.0f );
        input_flush_mouse_motion();
        sprintf ( name, "Mouse Joystick P0 Left Plus Up Additive" );
        expected = base_x - (int)(20.0f * g_pc_input.mouse.speed * 0.15f);
        reg_test ( name, "%d", &pokey.pc_pot[0], expected );
        sprintf ( name, "Mouse Joystick P0 Additive Leaves Vertical" );
        reg_test ( name, "%d", &pokey.pc_pot[1], base_y );

        p_config->input.game.players[0] = saved_p0;
        input_set_controls();
    }

    /*
     * Do Paddles for 800
     */
    else {
        p_config->input.game.players[0].type = CTRLR_TYPE_PADDLES;
        p_config->input.game.players[0].mouse_enable = 1;
        p_config->input.game.players[0].mouse_speed = 3;
        set_mouse_axis_input ( &p_config->input.game.players[0].paddles[0].left,
                0, DIR_MINUS );
        set_mouse_axis_input ( &p_config->input.game.players[0].paddles[0].right,
                0, DIR_PLUS );
        set_mouse_button_input ( &p_config->input.game.players[0].paddles[0].button,
                1 );
        input_set_controls();

        ball_test ( SDL_MOUSEMOTION, 0, 5, 20, 0, "Mouse Paddle P0 Right", "%d",
                &pokey.pc_pot[0],
                g_stick_interface[0].axis_value[0] +
                (int)(20.0f * g_pc_input.mouse.speed * 0.15f) );

        ball_test ( SDL_MOUSEMOTION, 0, 0, -60, 0, "Mouse Paddle P0 Left", "%d",
                &pokey.pc_pot[0],
                g_stick_interface[0].axis_value[0] -
                (int)(60.0f * g_pc_input.mouse.speed * 0.15f) );

        p_config->input.game.players[0].mouse_speed = 1;
        input_set_controls();
        for ( i = 0; i < 14; ++i )
            push_ball_event ( SDL_MOUSEMOTION, 0, 0, 0.5f, 0.0f );
        input_flush_mouse_motion();
        sprintf ( name, "Mouse Paddle P0 Fractional Right Speed1" );
        reg_test ( name, "%d", &pokey.pc_pot[0],
                test_pot_center ( 0, TEST_AXIS_H, 0 ) + 1 );

        p_config->input.game.players[0].mouse_speed = 3;
        input_set_controls();

        ball_test ( SDL_MOUSEMOTION, 0, 5, 800, 0,
                "Mouse Paddle P0 Clamp Hidden Right", "%d",
                &pokey.pc_pot[0], g_stick_interface[0].pot_high[TEST_AXIS_H] );
        int_test ( "Mouse Paddle P0 Hidden Right State", "%d",
                g_stick_interface[0].axis_value[0],
                g_stick_interface[0].pot_high[TEST_AXIS_H] );

        ball_test ( SDL_MOUSEMOTION, 0, 5, -10, 0,
                "Mouse Paddle P0 Reverse From Right", "%d",
                &pokey.pc_pot[0], g_stick_interface[0].pot_high[TEST_AXIS_H] -
                (int)(10.0f * g_pc_input.mouse.speed * 0.15f) );

        input_set_controls();
        ball_test ( SDL_MOUSEMOTION, 0, 5, -800, 0,
                "Mouse Paddle P0 Clamp Hidden Left", "%d",
                &pokey.pc_pot[0], g_stick_interface[0].pot_low[TEST_AXIS_H] );
        int_test ( "Mouse Paddle P0 Hidden Left State", "%d",
                g_stick_interface[0].axis_value[0],
                g_stick_interface[0].pot_low[TEST_AXIS_H] );

        ball_test ( SDL_MOUSEMOTION, 0, 5, 10, 0,
                "Mouse Paddle P0 Reverse From Left", "%d",
                &pokey.pc_pot[0], g_stick_interface[0].pot_low[TEST_AXIS_H] +
                (int)(10.0f * g_pc_input.mouse.speed * 0.15f) );

        button_test ( SDL_MOUSEBUTTONDOWN, 0, 1, "Mouse Paddle Button P0", "0x%02x",
                &pia.input[0], 0xfb );
        button_test ( SDL_MOUSEBUTTONUP, 0, 1, "Mouse Paddle Button P0", "0x%02x",
                &pia.input[0], 0xff );

        p_config->input.game.players[0].type = CTRLR_TYPE_JOYSTICK;
        input_set_controls();
    }

} /* end test_mouse_input */

/*
 * test_misc_input - misc stuff like failures that should happen
 */
void test_misc_input ( void ) {

    int i, j, pot, button, center, b800, max_con, value, combo;
    char name[257], tmp_string[257];
    t_config *p_config = config_get_global_cfg();

    if ( p_config->general.machine == MACHINE_TYPE_5200 ) {

        /*
         * Lets do fire button with combo enabled, should fail
         */
        event.type = SDL_JOYAXISMOTION;
        event.jaxis.which = 0;
        event.jaxis.axis = 4;
        event.jaxis.value = -20000;
        SDL_PushEvent(&event);
        pc_poll_input_events ( &event );

        sprintf ( name, "Joy P0 Bottom Button Fail", i );
        button_test ( SDL_JOYBUTTONDOWN, 0, 0, name, "%d",
                &gtia.trig[0], 1);
        button_test ( SDL_JOYBUTTONUP, 0, 0, name, "%d",
                &gtia.trig[0], 1);

        event.type = SDL_JOYAXISMOTION;
        event.jaxis.which = 0;
        event.jaxis.axis = 4;
        event.jaxis.value = 0;
        SDL_PushEvent(&event);
        pc_poll_input_events ( &event );

        /* Now it should pass */
        sprintf ( name, "Joy P0 Bottom Button Pass", i );
        button_test ( SDL_JOYBUTTONDOWN, 0, 0, name, "%d",
                &gtia.trig[0], 0);
        button_test ( SDL_JOYBUTTONUP, 0, 0, name, "%d",
                &gtia.trig[0], 1);
    }

} /* end test_misc_input */

/******************************************************************************
**  Function   :  test_auto_fire_input
**
**  Objective  :  Verify autofire edge capture, frame toggling, latched GTIA
**                release, and independent 8-bit paddle fire channels.
******************************************************************************/
void test_auto_fire_input ( void ) {

    t_config *p_config = config_get_global_cfg();

    p_config->input.game.players[0].type = CTRLR_TYPE_JOYSTICK;
    p_config->input.game.players[0].auto_fire = 1;
    p_config->input.game.players[0].auto_fire_speed = 5;
    input_set_controls();

    gtia.trig[0] = 1;
    gtia.trig_held_down[0] = 0;

    if ( p_config->general.machine == MACHINE_TYPE_5200 ) {
        gtia.latch_triggers = 0x04;

        button_test ( SDL_JOYBUTTONDOWN, 0, 0, "Auto Fire 5200 Down",
                "%d", &gtia.trig[0], 0 );
        SDL_Delay ( 120 );
        input_auto_fire_frame();
        reg_test ( "Auto Fire 5200 Latched Release", "%d",
                &gtia.trig[0], 1 );
        SDL_Delay ( 120 );
        input_auto_fire_frame();
        reg_test ( "Auto Fire 5200 Repress", "%d", &gtia.trig[0], 0 );
        button_test ( SDL_JOYBUTTONUP, 0, 0, "Auto Fire 5200 Up",
                "%d", &gtia.trig[0], 1 );

        gtia.latch_triggers = 0;
    }
    else {
        button_test ( SDL_JOYBUTTONDOWN, 0, 0, "Auto Fire 800 Joy Down",
                "%d", &gtia.trig[0], 0 );
        SDL_Delay ( 120 );
        input_auto_fire_frame();
        reg_test ( "Auto Fire 800 Joy Release", "%d", &gtia.trig[0], 1 );
        SDL_Delay ( 120 );
        input_auto_fire_frame();
        reg_test ( "Auto Fire 800 Joy Repress", "%d", &gtia.trig[0], 0 );
        button_test ( SDL_JOYBUTTONUP, 0, 0, "Auto Fire 800 Joy Up",
                "%d", &gtia.trig[0], 1 );

        p_config->input.game.players[0].type = CTRLR_TYPE_PADDLES;
        p_config->input.game.players[0].auto_fire = 1;
        p_config->input.game.players[0].auto_fire_speed = 5;
        set_joystick_button_input ( &p_config->input.game.players[0].paddles[0].button,
                0, 0 );
        set_joystick_button_input ( &p_config->input.game.players[0].paddles[1].button,
                1, 0 );
        pia.input[0] = 0xff;
        input_set_controls();

        button_test ( SDL_JOYBUTTONDOWN, 0, 0, "Auto Fire Paddle 1 Down",
                "0x%02x", &pia.input[0], 0xfb );
        button_test ( SDL_JOYBUTTONDOWN, 1, 0, "Auto Fire Paddle 2 Down",
                "0x%02x", &pia.input[0], 0xf3 );
        SDL_Delay ( 120 );
        input_auto_fire_frame();
        reg_test ( "Auto Fire Paddles Release", "0x%02x",
                &pia.input[0], 0xff );
        button_test ( SDL_JOYBUTTONUP, 0, 0, "Auto Fire Paddle 1 Up",
                "0x%02x", &pia.input[0], 0xff );
        SDL_Delay ( 120 );
        input_auto_fire_frame();
        reg_test ( "Auto Fire Paddle 2 Continues", "0x%02x",
                &pia.input[0], 0xf7 );
        button_test ( SDL_JOYBUTTONUP, 1, 0, "Auto Fire Paddle 2 Up",
                "0x%02x", &pia.input[0], 0xff );
    }

    p_config->input.game.players[0].type = CTRLR_TYPE_JOYSTICK;
    p_config->input.game.players[0].auto_fire = 0;
    p_config->input.game.players[0].auto_fire_speed = 3;
    input_set_controls();

} /* end test_auto_fire_input */

/*
 * test_none_controller_input - controller type None should look open on POKEY
 */
void test_none_controller_input ( void ) {

    int player = 0;
    int saved_type;
    int saved_controller_type;
    KBYTE saved_pot_enable;
    KBYTE read_value;
    char name[257];
    t_config *p_config = config_get_global_cfg();

    saved_type = p_config->input.game.players[player].type;
    saved_controller_type = pokey.controller_type[player];
    saved_pot_enable = gtia.pot_enable;

    p_config->input.game.players[player].type = CTRLR_TYPE_NONE;
    pokey_set_controller_type ( player, CTRL_BALL );
    pokey_set_pot_value ( player<<1, test_pot_center ( player, TEST_AXIS_H, 0 ) );
    pokey_set_pot_value ( (player<<1) | 0x1,
            test_pot_center ( player, TEST_AXIS_V, 0 ) );
    input_set_controls();

    sprintf ( name, "None P%d POT X PC", player );
    reg_test ( name, "%d", &pokey.pc_pot[player<<1], POT_OPEN_VALUE );
    sprintf ( name, "None P%d POT Y PC", player );
    reg_test ( name, "%d", &pokey.pc_pot[(player<<1) | 0x1], POT_OPEN_VALUE );

    pokey_init_scanline();
    sprintf ( name, "None P%d POT X Scan", player );
    reg_test ( name, "%d", &pokey.pot[player<<1], POT_OPEN_VALUE );
    sprintf ( name, "None P%d POT Y Scan", player );
    reg_test ( name, "%d", &pokey.pot[(player<<1) | 0x1], POT_OPEN_VALUE );

    if ( p_config->general.machine == MACHINE_TYPE_5200 ) {
        gtia.pot_enable = 0;
        read_value = pokey_read_register ( POT0 + (player<<1) );
        sprintf ( name, "None P%d POT X Disabled Read", player );
        reg_test ( name, "%d", &read_value, POT_OPEN_VALUE );
        read_value = pokey_read_register ( POT0 + ((player<<1) | 0x1) );
        sprintf ( name, "None P%d POT Y Disabled Read", player );
        reg_test ( name, "%d", &read_value, POT_OPEN_VALUE );
    }

    gtia.pot_enable = saved_pot_enable;
    pokey_set_controller_type ( player, saved_controller_type );
    p_config->input.game.players[player].type = saved_type;
    input_set_controls();

} /* end test_none_controller_input */

/*
 * test_pokey_5200_keypad_latch - 5200 KBCODE should latch scan aliases,
 * not synthesize changing bits during reads.
 */
void test_pokey_5200_keypad_latch ( void ) {

    int i, saw_low_alias, saw_high_alias, stable, fires, fires_off, fires_held;
    KBYTE fire_code[2];
    KBYTE read_value;
    char res_string[257], error[257];
    t_config *p_config = config_get_global_cfg();

    if ( p_config->general.machine != MACHINE_TYPE_5200 )
        return;

    pokey_reset();
    memset ( pokey.key_pressed, 0, sizeof(pokey.key_pressed) );
    memset ( pokey.shift_pressed, 0, sizeof(pokey.shift_pressed) );
    gtia.keypad = 0;
    pokey.current_key_code = 0;
    pokey.kbcode = 0;
    pokey.sk_ctl = SK_KEY_SCAN;

    pokey_set_keypad_down ( 0, KEY_START >> 1, 1 );
    saw_low_alias = 0;
    saw_high_alias = 0;
    for ( i = 0; i < 64; ++i ) {
        pokey_init_scanline();
        if ( pokey.kbcode == 0x18 || pokey.kbcode == 0x19 )
            saw_low_alias = 1;
        if ( pokey.kbcode == 0x38 || pokey.kbcode == 0x39 )
            saw_high_alias = 1;
    }

    sprintf ( res_string, "low=%d high=%d", saw_low_alias, saw_high_alias );
    sprintf ( error, "Should latch both low and high START scan aliases" );
    output_result ( saw_low_alias && saw_high_alias,
            "5200 Keypad START Scan Aliases", res_string, error );

    pokey_set_keypad_down ( 0, KEY_START >> 1, 0 );
    pokey.kbcode = KEY_START;
    stable = 1;
    read_value = KEY_START;
    for ( i = 0; i < 128; ++i ) {
        pokey_init_scanline();
        read_value = pokey_read_register ( KBCODE );
        if ( read_value != KEY_START ) {
            stable = 0;
            break;
        }
    }

    sprintf ( res_string, "0x%02x", read_value );
    sprintf ( error, "Should remain at latched KBCODE 0x%02x", KEY_START );
    output_result ( stable, "5200 KBCODE Stable After Release",
            res_string, error );

    /*
     * Held-key cadence: with debounce-disabled aliasing a held key reports once
     * per adjacent alias pair -> exactly twice per 64-line scan (every 32 lines),
     * not once per raw alias code (which would be 4).  Re-arm/ack the IRQ before
     * each scanline so every fresh fire is observable.
     */
    pokey_reset();
    memset ( pokey.key_pressed, 0, sizeof(pokey.key_pressed) );
    memset ( pokey.shift_pressed, 0, sizeof(pokey.shift_pressed) );
    gtia.keypad = 0;
    pokey.current_key_code = 0;
    pokey.sk_ctl = SK_KEY_SCAN;
    pokey.irq_enable |= IRQ_KEY_PRESS;
    pokey_set_keypad_down ( 0, KEY_START >> 1, 1 );

    fires = 0;
    memset ( fire_code, 0, sizeof(fire_code) );
    for ( i = 0; i < 64; ++i ) {
        pokey.irq_status |= IRQ_KEY_PRESS;      /* ack prior IRQ before scanline */
        pokey_init_scanline();
        if ( !(pokey.irq_status & IRQ_KEY_PRESS) ) {
            if ( fires < 2 )
                fire_code[fires] = pokey.kbcode;
            fires++;
        }
    }

    sprintf ( res_string, "fires=%d", fires );
    sprintf ( error, "Held key should fire once per alias pair (2 per scan)" );
    output_result ( fires == 2, "5200 Keypad Held-Key Cadence",
            res_string, error );

    sprintf ( res_string, "codes=0x%02x,0x%02x", fire_code[0], fire_code[1] );
    sprintf ( error, "Held START should latch 0x%02x then 0x%02x",
            KEY_START, KEY_START | 0x20 );
    output_result ( fire_code[0] == KEY_START &&
            fire_code[1] == (KEY_START | 0x20),
            "5200 Keypad Held-Key KBCODE", res_string, error );

    /*
     * Overrun: a second detection while a keypad IRQ is still active (never
     * acked) clears the SKSTAT overrun bit (active-low).
     */
    pokey_reset();
    memset ( pokey.key_pressed, 0, sizeof(pokey.key_pressed) );
    memset ( pokey.shift_pressed, 0, sizeof(pokey.shift_pressed) );
    gtia.keypad = 0;
    pokey.current_key_code = 0;
    pokey.sk_ctl = SK_KEY_SCAN;
    pokey.irq_enable |= IRQ_KEY_PRESS;
    pokey.irq_status |= IRQ_KEY_PRESS;                  /* no IRQ pending yet   */
    pokey.sk_status |= SK_KEY_OVERRUN;                  /* no overrun yet       */
    pokey_set_keypad_down ( 0, KEY_START >> 1, 1 );

    for ( i = 0; i < 64; ++i )                          /* never ack the IRQ    */
        pokey_init_scanline();

    sprintf ( res_string, "sk_status=0x%02x", pokey.sk_status );
    sprintf ( error, "Unacked second detection should set overrun (bit clear)" );
    output_result ( !(pokey.sk_status & SK_KEY_OVERRUN),
            "5200 Keypad Overrun", res_string, error );

    /*
     * Top button (KR2) reads as SHIFT+CONTROL: KBCODE bits 6/7 are set on the
     * latched keypad code while it is held.
     */
    pokey_reset();
    memset ( pokey.key_pressed, 0, sizeof(pokey.key_pressed) );
    memset ( pokey.shift_pressed, 0, sizeof(pokey.shift_pressed) );
    gtia.keypad = 0;
    pokey.current_key_code = 0;
    pokey.kbcode = 0;
    pokey.sk_ctl = SK_KEY_SCAN;
    pokey_set_keypad_down ( 0, KEY_START >> 1, 1 );
    pokey_set_top_button_down ( 0, 1 );

    for ( i = 0; i < 64; ++i )
        pokey_init_scanline();

    sprintf ( res_string, "kbcode=0x%02x", pokey.kbcode );
    sprintf ( error, "Top button should set KBCODE bits 6/7" );
    output_result ( (pokey.kbcode & (KB_SHIFT_KEY | KB_CTRL_KEY))
            == (KB_SHIFT_KEY | KB_CTRL_KEY),
            "5200 Keypad Top-Button KBCODE Bits", res_string, error );

    /*
     * Debounce gate (Star Raiders technique, doc/altirra.txt:19852): software
     * runs with debounce off to detect a key, then enables SKCTL debounce in the
     * keypad handler (writes $03) to stop repeats until release.  Once debounce
     * is on, a still-held key must NOT generate further keypad IRQs -- otherwise
     * the repeated debounce-on KBCODE reads ($ff) are mis-decoded by the BIOS as
     * a different key, clobbering the real keypress (START never registers).
     */
    pokey_reset();
    memset ( pokey.key_pressed, 0, sizeof(pokey.key_pressed) );
    memset ( pokey.shift_pressed, 0, sizeof(pokey.shift_pressed) );
    gtia.keypad = 0;
    pokey.current_key_code = 0;
    pokey.sk_ctl = SK_KEY_SCAN;                         /* debounce off         */
    pokey.irq_enable |= IRQ_KEY_PRESS;
    pokey_set_keypad_down ( 0, KEY_START >> 1, 1 );

    fires_off = 0;                                      /* phase 1: debounce off */
    for ( i = 0; i < 64; ++i ) {
        pokey.irq_status |= IRQ_KEY_PRESS;
        pokey_init_scanline();
        if ( !(pokey.irq_status & IRQ_KEY_PRESS) )
            fires_off++;
    }

    pokey.sk_ctl = SK_KEY_SCAN | SK_KEY_DEB;            /* phase 2: debounce on  */
    fires_held = 0;                                     /* key still held        */
    for ( i = 0; i < 128; ++i ) {
        pokey.irq_status |= IRQ_KEY_PRESS;
        pokey_init_scanline();
        if ( !(pokey.irq_status & IRQ_KEY_PRESS) )
            fires_held++;
    }

    sprintf ( res_string, "off=%d held=%d", fires_off, fires_held );
    sprintf ( error, "Debounce-on held key must stop firing keypad IRQ" );
    output_result ( fires_off > 0 && fires_held == 0,
            "5200 Keypad Debounce Gate", res_string, error );

    /*
     * With debounce still on, a release + fresh press is a new rising edge and
     * must report again (one IRQ), so input keeps working after the gate.
     */
    pokey_set_keypad_down ( 0, KEY_START >> 1, 0 );
    for ( i = 0; i < 64; ++i ) {                        /* let release register  */
        pokey.irq_status |= IRQ_KEY_PRESS;
        pokey_init_scanline();
    }
    pokey_set_keypad_down ( 0, KEY_START >> 1, 1 );
    fires = 0;
    for ( i = 0; i < 64; ++i ) {
        pokey.irq_status |= IRQ_KEY_PRESS;
        pokey_init_scanline();
        if ( !(pokey.irq_status & IRQ_KEY_PRESS) )
            fires++;
    }

    sprintf ( res_string, "fires=%d", fires );
    sprintf ( error, "Fresh press after debounce gate should report once" );
    output_result ( fires == 1, "5200 Keypad Debounce Re-Press",
            res_string, error );

} /* end test_pokey_5200_keypad_latch */
