#include "game/options_menu.h"

#include "affine_background.h"
#include "audio_utils.h"
#include "background_options_menu_gfx.h"
#include "button.h"
#include "card.h"
#include "game.h"
#include "game/common_ui.h"
#include "game_variables.h"
#include "graphic_utils.h"
#include "save.h"
#include "selection_grid.h"
#include "soundbank.h"
#include "util.h"

#include <stdint.h>
#include <string.h>
#include <tonc.h>
#include <tonc_math.h>
#include <tonc_memdef.h>

enum OptionButtonRows
{
    GAME_SPEED_ROW_IDX,
    HIGH_CONTRAST_ROW_IDX,
    MUSIC_VOLUME_ROW_IDX,
    SOUND_VOLUME_ROW_IDX,
    SAVE_BACK_ROW_IDX,
    NB_OPTIONS_ROWS
};
enum OptionSpeedButtons
{
    GAME_SPEED_DOWN_BTN_IDX,
    GAME_SPEED_UP_BTN_IDX,
    NB_GAME_SPEED_BUTTONS
};

// Color palette indices
#define MENU_BUTTON_MAIN_COLOR_PAL_IDX          1
#define SAVE_BUTTON_MAIN_COLOR_PAL_IDX          3
#define BACK_BUTTON_MAIN_COLOR_PAL_IDX          4
#define SPEED_DOWN_BUTTON_OUTLINE_COLOR_PAL_IDX 16
#define SPEED_BUTTON_OUTLINE_COLOR_PAL_IDX      17
#define SPEED_UP_BUTTON_OUTLINE_COLOR_PAL_IDX   18
#define CONTRAST_BUTTON_OUTLINE_COLOR_PAL_IDX   19
#define READABLE_BUTTON_OUTLINE_COLOR_PAL_IDX   20
#define MUSIC_BUTTON_OUTLINE_COLOR_PAL_IDX      21
#define SOUND_BUTTON_OUTLINE_COLOR_PAL_IDX      22
#define SAVE_BUTTON_OUTLINE_COLOR_PAL_IDX       23
#define BACK_BUTTON_OUTLINE_COLOR_PAL_IDX       24

// Define selection grid for the menu buttons

static void game_speed_down_on_pressed(void);
static void game_speed_up_on_pressed(void);
static void high_contrast_on_pressed(void);
static void more_readable_on_pressed(void);
static void save_on_pressed(void);
static void back_on_pressed(void);
static int options_menu_return_upper_rows_size(void);
static int options_menu_return_card_sprites_row_size(void);
static int options_menu_return_bottom_row_size(void);
static void options_menu_row_on_key_transit(SelectionGrid* selection_grid, Selection* selection);
static bool game_speed_row_on_selection_changed(
    SelectionGrid* selection_grid,
    int row_idx,
    const Selection* prev_selection,
    const Selection* new_selection
);
static bool music_volume_row_on_selection_changed(
    SelectionGrid* selection_grid,
    int row_idx,
    const Selection* prev_selection,
    const Selection* new_selection
);
static bool sound_volume_row_on_selection_changed(
    SelectionGrid* selection_grid,
    int row_idx,
    const Selection* prev_selection,
    const Selection* new_selection
);
static bool regular_button_row_on_selection_changed(
    SelectionGrid* selection_grid,
    int row_idx,
    const Selection* prev_selection,
    const Selection* new_selection
);

// clang-format off
static SelectionGridRow options_menu_selection_rows[] = {
    {
        GAME_SPEED_ROW_IDX,
        options_menu_return_upper_rows_size,
        game_speed_row_on_selection_changed,
        NULL,
        {.wrap = true} // The wrapping is used to allow button activation with Left/Right arrows by
                       // putting logic into the `game_speed_row_on_selection_changed` function
    },
    {
        HIGH_CONTRAST_ROW_IDX,
        options_menu_return_card_sprites_row_size,
        regular_button_row_on_selection_changed,
        options_menu_row_on_key_transit,
        {.wrap = false}
    },
    {
        MUSIC_VOLUME_ROW_IDX,
        options_menu_return_upper_rows_size,
        music_volume_row_on_selection_changed,
        NULL,
        {.wrap = true} // Same trick as Game Speed button
    },
    {
        SOUND_VOLUME_ROW_IDX,
        options_menu_return_upper_rows_size,
        sound_volume_row_on_selection_changed,
        NULL,
        {.wrap = true} // Same trick as Game Speed button
    },
    {
        SAVE_BACK_ROW_IDX,
        options_menu_return_bottom_row_size,
        regular_button_row_on_selection_changed,
        options_menu_row_on_key_transit,
        {.wrap = false}
    }
};

static Button options_menu_buttons[NB_OPTIONS_ROWS][2] = {
    {{SPEED_BUTTON_OUTLINE_COLOR_PAL_IDX,    MENU_BUTTON_MAIN_COLOR_PAL_IDX, NULL,                     NULL}},
    {
        {CONTRAST_BUTTON_OUTLINE_COLOR_PAL_IDX, MENU_BUTTON_MAIN_COLOR_PAL_IDX, high_contrast_on_pressed, NULL},
        {READABLE_BUTTON_OUTLINE_COLOR_PAL_IDX, MENU_BUTTON_MAIN_COLOR_PAL_IDX, more_readable_on_pressed, NULL}
    },
    {{MUSIC_BUTTON_OUTLINE_COLOR_PAL_IDX,    MENU_BUTTON_MAIN_COLOR_PAL_IDX, NULL,                     NULL}},
    {{SOUND_BUTTON_OUTLINE_COLOR_PAL_IDX,    MENU_BUTTON_MAIN_COLOR_PAL_IDX, NULL,                     NULL}},
    {
        {SAVE_BUTTON_OUTLINE_COLOR_PAL_IDX, SAVE_BUTTON_MAIN_COLOR_PAL_IDX, save_on_pressed, NULL},
        {BACK_BUTTON_OUTLINE_COLOR_PAL_IDX, BACK_BUTTON_MAIN_COLOR_PAL_IDX, back_on_pressed, NULL},
    }
};

static Button game_speed_buttons[] = {
    {SPEED_DOWN_BUTTON_OUTLINE_COLOR_PAL_IDX, MENU_BUTTON_MAIN_COLOR_PAL_IDX, game_speed_down_on_pressed, NULL},
    {SPEED_UP_BUTTON_OUTLINE_COLOR_PAL_IDX,   MENU_BUTTON_MAIN_COLOR_PAL_IDX, game_speed_up_on_pressed,   NULL},
};
// clang-format on

const Selection OPTIONS_MENU_INIT_SEL = {0, 0};

static SelectionGrid options_menu_selection_grid = {
    options_menu_selection_rows,
    NB_OPTIONS_ROWS,
    OPTIONS_MENU_INIT_SEL
};

// Positions/Rects used to construct and update the menu
// clang-format off

// Values in tiles
static const Rect     OPTIONS_SPEED_DOWN_ACTIVE_BTN_SRC_RECT   = { 0, 20,  0, 21};
static const Rect     OPTIONS_SPEED_DOWN_DISABLED_BTN_SRC_RECT = { 6, 22,  6, 23};
static const BG_POINT OPTIONS_SPEED_DOWN_BTN_DEST_POS          = {11,  3};
static const Rect     OPTIONS_SPEED_UP_ACTIVE_BTN_SRC_RECT     = { 7, 20,  7, 21};
static const Rect     OPTIONS_SPEED_UP_DISABLED_BTN_SRC_RECT   = { 7, 22,  7, 23};
static const BG_POINT OPTIONS_SPEED_UP_BTN_DEST_POS            = {18,  3};
static const Rect     OPTIONS_SPEED_VALUES[GAME_SPEED_MAX]   = { { 3, 20,  4, 21},
                                                                 { 0, 22,  1, 23},
                                                                 { 2, 22,  3, 23},
                                                                 { 4, 22,  5, 23} };
static const BG_POINT OPTIONS_SPEED_VALUE_DEST_POS             = {14,  3};

static const Rect     OPTIONS_CONTRAST_CHECK_NO_SRC_RECT       = { 0, 24,  1, 25};
static const Rect     OPTIONS_CONTRAST_CHECK_YES_SRC_RECT      = { 2, 24,  3, 25};
static const BG_POINT OPTIONS_CONTRAST_CHECK_DEST_POS          = {14,  7};
static const Rect     OPTIONS_READABLE_CHECK_NO_SRC_RECT       = { 4, 24,  5, 25};
static const Rect     OPTIONS_READABLE_CHECK_YES_SRC_RECT      = { 6, 24,  7, 25};
static const BG_POINT OPTIONS_READABLE_CHECK_DEST_POS          = {21,  7};

static const Rect     OPTIONS_MUSIC_SLIDER_FULL_SRC            = { 9, 20,  9, 20};
static const Rect     OPTIONS_MUSIC_SLIDER_MID_SRC             = {10, 20, 10, 20};
static const Rect     OPTIONS_MUSIC_SLIDER_EMPTY_SRC           = {11, 20, 11, 20};
static const Rect     OPTIONS_SOUND_SLIDER_FULL_SRC            = { 9, 22,  9, 22};
static const Rect     OPTIONS_SOUND_SLIDER_MID_SRC             = {10, 22, 10, 22};
static const Rect     OPTIONS_SOUND_SLIDER_EMPTY_SRC           = {11, 22, 11, 22};
static const BG_POINT OPTIONS_MUSIC_SLIDER_START_POS           = { 5, 11};
static const BG_POINT OPTIONS_SOUND_SLIDER_START_POS           = { 5, 14};
static const BG_POINT OPTIONS_MUSIC_SLIDER_END_POS             = {24, 11};
static const BG_POINT OPTIONS_SOUND_SLIDER_END_POS             = {24, 14};
static const u8       OPTIONS_MUSIC_SLIDER_SEGMENT_LENGTH      = (OPTIONS_MUSIC_SLIDER_END_POS.x - OPTIONS_MUSIC_SLIDER_START_POS.x + 1)
                                                                    / VOLUME_OPTION_MAX;
static const u8       OPTIONS_SOUND_SLIDER_SEGMENT_LENGTH      = (OPTIONS_SOUND_SLIDER_END_POS.x - OPTIONS_SOUND_SLIDER_START_POS.x + 1)
                                                                    / VOLUME_OPTION_MAX;

// Values in pixels
static const BG_POINT OPTIONS_GAME_SPEED_TEXT_POS        = { 82,  16};
static const BG_POINT OPTIONS_CARD_SPRITES_TEXT_POS      = { 72,  48};
static const BG_POINT OPTIONS_MUSIC_VOLUME_TEXT_POS      = { 56,  80};
static const BG_POINT OPTIONS_MUSIC_VALUE_TEXT_POS       = {160,  80};
static const BG_POINT OPTIONS_SOUND_VOLUME_TEXT_POS      = { 56, 104};
static const BG_POINT OPTIONS_SOUND_VALUE_TEXT_POS       = {160, 104};
static const BG_POINT OPTIONS_BACK_SAVE_TEXT_POS         = { 64, 136};
// clang-format on

#define GAME_SPEED_ARROW_HIGHLIGHT_DURATION 10
enum OptionSpeedButtons game_speed_arrow_highlight_button = GAME_SPEED_UP_BTN_IDX;
static s32 game_speed_arrow_highlight_start = UNDEFINED;

static void disable_all_game_speed_outlines_except_self(enum OptionSpeedButtons highlighted_btn)
{
    for (int i = 0; i < NB_GAME_SPEED_BUTTONS; i++)
    {
        button_set_highlight(&game_speed_buttons[i], i == highlighted_btn);
    }
}

static void disable_all_outlines_except_self(Selection sel_btn)
{
    // These two get disabled no matter what here
    disable_all_game_speed_outlines_except_self(NB_GAME_SPEED_BUTTONS);

    for (int j = 0; j < NB_OPTIONS_ROWS; j++)
    {
        int nb_buttons_in_row;

        switch (j)
        {
            case HIGH_CONTRAST_ROW_IDX:
                nb_buttons_in_row = options_menu_return_card_sprites_row_size();
                break;
            case SAVE_BACK_ROW_IDX:
                nb_buttons_in_row = options_menu_return_bottom_row_size();
                break;
            default:
                nb_buttons_in_row = options_menu_return_upper_rows_size();
                break;
        }

        for (int i = 0; i < nb_buttons_in_row; i++)
        {
            button_set_highlight(&options_menu_buttons[j][i], i == sel_btn.x && j == sel_btn.y);
        }
    }
}

static void update_game_speed_button_graphics()
{
    // check if need to disable game speed arrows

    Rect speed_down_btn_tiles = (g_game_vars.game_speed == GAME_SPEED_MIN)
                                  ? OPTIONS_SPEED_DOWN_DISABLED_BTN_SRC_RECT
                                  : OPTIONS_SPEED_DOWN_ACTIVE_BTN_SRC_RECT;
    main_bg_se_copy_rect(speed_down_btn_tiles, OPTIONS_SPEED_DOWN_BTN_DEST_POS);

    Rect speed_up_btn_tiles = (g_game_vars.game_speed == GAME_SPEED_MAX)
                                ? OPTIONS_SPEED_UP_DISABLED_BTN_SRC_RECT
                                : OPTIONS_SPEED_UP_ACTIVE_BTN_SRC_RECT;
    main_bg_se_copy_rect(speed_up_btn_tiles, OPTIONS_SPEED_UP_BTN_DEST_POS);

    main_bg_se_copy_rect(
        OPTIONS_SPEED_VALUES[g_game_vars.game_speed - 1],
        OPTIONS_SPEED_VALUE_DEST_POS
    );
}

static void update_high_contrast_button_graphics(void)
{
    Rect contrast_btn_tiles = (get_cards_high_contrast()) ? OPTIONS_CONTRAST_CHECK_YES_SRC_RECT
                                                          : OPTIONS_CONTRAST_CHECK_NO_SRC_RECT;
    main_bg_se_copy_rect(contrast_btn_tiles, OPTIONS_CONTRAST_CHECK_DEST_POS);
}

static void update_more_readable_button_graphics(void)
{
    Rect readable_btn_tiles = (get_cards_more_readable()) ? OPTIONS_READABLE_CHECK_YES_SRC_RECT
                                                          : OPTIONS_READABLE_CHECK_NO_SRC_RECT;
    main_bg_se_copy_rect(readable_btn_tiles, OPTIONS_READABLE_CHECK_DEST_POS);
}

static void update_volume_slider_graphics(enum OptionButtonRows sel_row)
{
    u8 slider_value;
    BG_POINT slider_segment_dest;
    int slider_segment_length;
    Rect slider_segment_full;
    Rect slider_segment_mid;
    Rect slider_segment_empty;
    BG_POINT slider_text_pos;

    switch (sel_row)
    {
        case MUSIC_VOLUME_ROW_IDX:
            slider_value = g_game_vars.music_volume;
            slider_segment_dest = OPTIONS_MUSIC_SLIDER_START_POS;
            slider_segment_length = OPTIONS_MUSIC_SLIDER_SEGMENT_LENGTH;
            slider_segment_full = OPTIONS_MUSIC_SLIDER_FULL_SRC;
            slider_segment_mid = OPTIONS_MUSIC_SLIDER_MID_SRC;
            slider_segment_empty = OPTIONS_MUSIC_SLIDER_EMPTY_SRC;
            slider_text_pos = OPTIONS_MUSIC_VALUE_TEXT_POS;
            break;
        case SOUND_VOLUME_ROW_IDX:
            slider_value = g_game_vars.sound_volume;
            slider_segment_dest = OPTIONS_SOUND_SLIDER_START_POS;
            slider_segment_length = OPTIONS_SOUND_SLIDER_SEGMENT_LENGTH;
            slider_segment_full = OPTIONS_SOUND_SLIDER_FULL_SRC;
            slider_segment_mid = OPTIONS_SOUND_SLIDER_MID_SRC;
            slider_segment_empty = OPTIONS_SOUND_SLIDER_EMPTY_SRC;
            slider_text_pos = OPTIONS_SOUND_VALUE_TEXT_POS;
            break;
        // This function only takes in a volume slider
        default:
            return;
    }

    int i = 0;

    // full part of the bar
    for (; i < slider_value * slider_segment_length - 1; i++)
    {
        main_bg_se_copy_rect(slider_segment_full, slider_segment_dest);
        slider_segment_dest.x++;
    }

    // at exactly `slider_value` we either:
    //  - are in the middle of the bar, then we draw the frontier between full and empty tiles
    //  - are at the end, then draw a full segment
    if (slider_value == VOLUME_OPTION_MAX)
    {
        main_bg_se_copy_rect(slider_segment_full, slider_segment_dest);
    }
    else
    {
        if (slider_value != VOLUME_OPTION_MIN)
        {
            // draw middle point
            main_bg_se_copy_rect(slider_segment_mid, slider_segment_dest);
            i++;
            slider_segment_dest.x++;
        }

        // empty part of the bar
        for (; i < VOLUME_OPTION_MAX * slider_segment_length; i++)
        {
            main_bg_se_copy_rect(slider_segment_empty, slider_segment_dest);
            slider_segment_dest.x++;
        }
    }

    tte_printf(
        "#{P:%d,%d; cx:0x%X000}%3d",
        slider_text_pos.x,
        slider_text_pos.y,
        TTE_WHITE_PB,
        (slider_value * VOLUME_OPTION_INCREMENT)
    );
}

void game_options_menu_change_background(void)
{
    tte_erase_screen();
    CBB_CLEAR(MAIN_BG_CBB);

    GRIT_CPY(pal_bg_mem, background_options_menu_gfxPal);
    GRIT_CPY(&tile_mem[MAIN_BG_CBB], background_options_menu_gfxTiles);
    GRIT_CPY(&se_mem[MAIN_BG_SBB], background_options_menu_gfxMap);

    tte_printf(
        "#{P:%d,%d; cx:0x%X000}Game Speed",
        OPTIONS_GAME_SPEED_TEXT_POS.x,
        OPTIONS_GAME_SPEED_TEXT_POS.y,
        TTE_WHITE_PB
    );
    tte_printf(
        "#{P:%d,%d; cx:0x%X000}Card Sprites",
        OPTIONS_CARD_SPRITES_TEXT_POS.x,
        OPTIONS_CARD_SPRITES_TEXT_POS.y,
        TTE_WHITE_PB
    );
    tte_printf(
        "#{P:%d,%d; cx:0x%X000}Music Volume",
        OPTIONS_MUSIC_VOLUME_TEXT_POS.x,
        OPTIONS_MUSIC_VOLUME_TEXT_POS.y,
        TTE_WHITE_PB
    );
    tte_printf(
        "#{P:%d,%d; cx:0x%X000}Sound Volume",
        OPTIONS_SOUND_VOLUME_TEXT_POS.x,
        OPTIONS_SOUND_VOLUME_TEXT_POS.y,
        TTE_WHITE_PB
    );
    tte_printf(
        "#{P:%d,%d; cx:0x%X000}Save     Cancel",
        OPTIONS_BACK_SAVE_TEXT_POS.x,
        OPTIONS_BACK_SAVE_TEXT_POS.y,
        TTE_WHITE_PB
    );
}

void game_options_menu_on_init(void)
{
    change_background(BG_OPTIONS_MENU, false);

    // Select game speed button by default
    options_menu_selection_grid.selection = OPTIONS_MENU_INIT_SEL;
    disable_all_outlines_except_self(OPTIONS_MENU_INIT_SEL);

    // Do an update on the first frame
    update_game_speed_button_graphics();
    update_high_contrast_button_graphics();
    update_more_readable_button_graphics();
    update_volume_slider_graphics(MUSIC_VOLUME_ROW_IDX);
    update_volume_slider_graphics(SOUND_VOLUME_ROW_IDX);
}

void game_options_menu_on_update(void)
{
    selection_grid_process_input(&options_menu_selection_grid);

    // game speed arrows small animation: they stay highlighted for a few frames
    if (game_speed_arrow_highlight_start != UNDEFINED &&
        (g_game_vars.timer - game_speed_arrow_highlight_start) >
            GAME_SPEED_ARROW_HIGHLIGHT_DURATION)
    {
        game_speed_arrow_highlight_start = UNDEFINED;
        button_set_highlight(&game_speed_buttons[game_speed_arrow_highlight_button], false);
    }
}

void game_options_menu_on_exit(void)
{
    tte_erase_screen();
}

static void game_speed_down_on_pressed(void)
{
    g_game_vars.game_speed--;
    game_speed_arrow_highlight_start = g_game_vars.timer;
    game_speed_arrow_highlight_button = GAME_SPEED_DOWN_BTN_IDX;
    disable_all_game_speed_outlines_except_self(GAME_SPEED_DOWN_BTN_IDX);
    update_game_speed_button_graphics();
}

static void game_speed_up_on_pressed(void)
{
    g_game_vars.game_speed++;
    game_speed_arrow_highlight_start = g_game_vars.timer;
    game_speed_arrow_highlight_button = GAME_SPEED_UP_BTN_IDX;
    disable_all_game_speed_outlines_except_self(GAME_SPEED_UP_BTN_IDX);
    update_game_speed_button_graphics();
}

/**
 * @brief Handles input for the high contrast card toggle button and nothing more.
 */
static void high_contrast_on_pressed(void)
{
    set_cards_high_contrast(!get_cards_high_contrast());
    update_high_contrast_button_graphics();
}

/**
 * @brief Handles input for the more readable card toggle button and nothing more.
 */
static void more_readable_on_pressed(void)
{
    set_cards_more_readable(!get_cards_more_readable());
    update_more_readable_button_graphics();
}

/**
 * @brief Handles input for the Save button.
 */
static void save_on_pressed(void)
{
    save_options();
    game_change_state(GAME_STATE_MAIN_MENU);
}

/**
 * @brief Handles input for the Back button.
 */
static void back_on_pressed(void)
{
    load_options();
    game_change_state(GAME_STATE_MAIN_MENU);
}

/**
 * @brief Gives the width of normal options rows in selection grid.
 *
 * @returns 1
 */
static int options_menu_return_upper_rows_size(void)
{
    return 1;
}

/**
 * @brief Gives the width of Card Sprites options row in selection grid.
 *
 * @returns 2
 */
static int options_menu_return_card_sprites_row_size(void)
{
    return 2;
}

/**
 * @brief Gives the width of the Save and Back buttons' row.
 *
 * @returns 2
 */
static int options_menu_return_bottom_row_size(void)
{
    return 2;
}

static void change_button_highlight(
    int row_idx,
    const Selection* prev_selection,
    const Selection* new_selection
)
{
    if (prev_selection->y == row_idx)
    {
        button_set_highlight(&options_menu_buttons[row_idx][prev_selection->x], false);
    }

    if (new_selection->y == row_idx)
    {
        play_sfx(SFX_BUTTON, MM_BASE_PITCH_RATE, BUTTON_SFX_VOLUME);
        button_set_highlight(&options_menu_buttons[row_idx][new_selection->x], true);
    }
}

/**
 * @brief Handles left/Right inputs on the row of the Game Speed button.
 *        Is used to increased/decrease the speed value, instead of relying on a
 *        `on_key_transit` function because the latter does not take directional
 *        inputs.
 */
static bool game_speed_row_on_selection_changed(
    SelectionGrid* selection_grid,
    int row_idx,
    const Selection* prev_selection,
    const Selection* new_selection
)
{
    change_button_highlight(row_idx, prev_selection, new_selection);
    disable_all_game_speed_outlines_except_self(NB_GAME_SPEED_BUTTONS);

    // Can only change game speed by pressing left/right while staying on the button's row
    if (prev_selection->y != new_selection->y)
        return true;

    if (key_hit(KEY_LEFT) && g_game_vars.game_speed > GAME_SPEED_MIN)
    {
        button_press(&game_speed_buttons[GAME_SPEED_DOWN_BTN_IDX]);
    }
    else if (key_hit(KEY_RIGHT) && g_game_vars.game_speed < GAME_SPEED_MAX)
    {
        button_press(&game_speed_buttons[GAME_SPEED_UP_BTN_IDX]);
    }

    return true;
}

/**
 * @brief Handles input for all SelectionGridRows in options menu, only actually does
 *        something on rows containing "normal" buttons (those for which the on_pressed
 *        method in options_menu_buttons is not NULL).
 */
static void options_menu_row_on_key_transit(SelectionGrid* selection_grid, Selection* selection)
{
    if (key_hit(SELECT_CARD))
    {
        button_press(&options_menu_buttons[selection->y][selection->x]);
    }
}

static bool regular_button_row_on_selection_changed(
    SelectionGrid* selection_grid,
    int row_idx,
    const Selection* prev_selection,
    const Selection* new_selection
)
{
    change_button_highlight(row_idx, prev_selection, new_selection);
    return true;
}

static bool music_volume_row_on_selection_changed(
    SelectionGrid* selection_grid,
    int row_idx,
    const Selection* prev_selection,
    const Selection* new_selection
)
{
    change_button_highlight(row_idx, prev_selection, new_selection);

    if (prev_selection->y != new_selection->y)
        return true;

    if (key_hit(KEY_LEFT) && g_game_vars.music_volume > VOLUME_OPTION_MIN)
    {
        g_game_vars.music_volume--;
    }
    else if (key_hit(KEY_RIGHT) && g_game_vars.music_volume < VOLUME_OPTION_MAX)
    {
        g_game_vars.music_volume++;
    }

    update_volume_slider_graphics(MUSIC_VOLUME_ROW_IDX);
    set_volume(volume_module_step_to_val(g_game_vars.music_volume));

    return true;
}

static bool sound_volume_row_on_selection_changed(
    SelectionGrid* selection_grid,
    int row_idx,
    const Selection* prev_selection,
    const Selection* new_selection
)
{
    change_button_highlight(row_idx, prev_selection, new_selection);

    if (prev_selection->y != new_selection->y)
        return true;

    if (key_hit(KEY_LEFT) && g_game_vars.sound_volume > VOLUME_OPTION_MIN)
    {
        g_game_vars.sound_volume--;
    }
    else if (key_hit(KEY_RIGHT) && g_game_vars.sound_volume < VOLUME_OPTION_MAX)
    {
        g_game_vars.sound_volume++;
    }

    update_volume_slider_graphics(SOUND_VOLUME_ROW_IDX);

    return true;
}
