/**
 * @file hand.c
 *
 * @brief Implementation of functions relative to manipulating and analyzing the
 *         contents of the Hand.
 */
#include "hand.h"

#include "audio_utils.h"
#include "card.h"
#include "game.h"
#include "game_variables.h"
#include "graphic_utils.h"
#include "soundbank.h"
#include "util.h"

#include <tonc.h>

typedef struct
{
    u32 chips;
    u32 mult;
    char* display_name;
} HandValues;

static const HandValues hand_base_values[] = {
    {.chips = 0,   .mult = 0,  .display_name = NULL     }, // NONE
    {.chips = 5,   .mult = 1,  .display_name = "Hi-Card"}, // HIGH_CARD
    {.chips = 10,  .mult = 2,  .display_name = "Pair"   }, // PAIR
    {.chips = 20,  .mult = 2,  .display_name = "2 Pair" }, // TWO_PAIR
    {.chips = 30,  .mult = 3,  .display_name = "3 OAK"  }, // THREE_OF_A_KIND
    {.chips = 30,  .mult = 4,  .display_name = "Strt"   }, // STRAIGHT
    {.chips = 35,  .mult = 4,  .display_name = "Flush"  }, // FLUSH
    {.chips = 40,  .mult = 4,  .display_name = "Full H" }, // FULL_HOUSE
    {.chips = 60,  .mult = 7,  .display_name = "4 OAK"  }, // FOUR_OF_A_KIND
    {.chips = 100, .mult = 8,  .display_name = "Strt F" }, // STRAIGHT_FLUSH
    {.chips = 100, .mult = 8,  .display_name = "Royal F"}, // ROYAL_FLUSH
    {.chips = 120, .mult = 12, .display_name = "5 OAK"  }, // FIVE_OF_A_KIND
    {.chips = 140, .mult = 14, .display_name = "Flush H"}, // FLUSH_HOUSE
    {.chips = 160, .mult = 16, .display_name = "Flush 5"}  // FLUSH_FIVE
};

// clang-format off
// Rects for TTE (in pixels)        left   top    right  bottom
static const Rect HAND_TYPE_RECT = {8,     64,    64,    72};
// clang-format on

typedef struct Hand
{
    // Hand stack
    CardObject* cards[MAX_HAND_SIZE];
    s32 hand_top;        // Position of the last card in hand array, -1 when no card in hand
    s32 hand_selections; // Number of selected Cards.

    // Hand Type
    enum HandType hand_type;
    ContainedHandTypes contained_hands;

    enum HandState state;
    bool sort_by_suit;
} Hand;

static Hand hand = {
    .cards = {NULL},
    .hand_top = -1,
    .hand_selections = 0,
    .hand_type = NONE,
    .contained_hands = {{{0}}},
    .state = HAND_DRAW,
    .sort_by_suit = false
};

// Forward declarations
static ContainedHandTypes compute_contained_hand_types(void);
static enum HandType compute_hand_type(struct ContainedHandTypes contained_types);

// Hand Struct Manipulation

enum HandState get_hand_state(void)
{
    return hand.state;
}

void set_hand_state(enum HandState new_hand_state)
{
    hand.state = new_hand_state;
}

CardObject** get_hand_array(void)
{
    return hand.cards;
}

int get_hand_top(void)
{
    return hand.hand_top;
}

void set_hand_top(int new_hand_top)
{
    hand.hand_top = new_hand_top;
}

int hand_nb_held_cards(void)
{
    return hand.hand_top + 1;
}

int hand_get_nb_selected_cards(void)
{
    return hand.hand_selections;
}

void hand_set_nb_selected_cards(int new_selections)
{
    hand.hand_selections = new_selections;
}

enum HandType get_hand_type(void)
{
    return hand.hand_type;
}

ContainedHandTypes* get_contained_hands(void)
{
    return &hand.contained_hands;
}

static void print_hand_type(const char* hand_type_str)
{
    if (hand_type_str == NULL)
        return; // NULL-checking paranoia

    Rect hand_type_rect = HAND_TYPE_RECT;
    update_text_rect_to_center_str(&hand_type_rect, hand_type_str, SCREEN_LEFT);
    tte_printf(
        "#{P:%d,%d; cx:0x%X000}%s",
        hand_type_rect.left,
        hand_type_rect.top,
        TTE_WHITE_PB,
        hand_type_str
    );
}

void compute_hand_value_info(void)
{
    tte_erase_rect_wrapper(HAND_TYPE_RECT);
    hand.contained_hands = compute_contained_hand_types();
    hand.hand_type = compute_hand_type(hand.contained_hands);

    HandValues hand_values = hand_base_values[hand.hand_type];

    set_chips(hand_values.chips);
    set_mult(hand_values.mult);

    print_hand_type(hand_values.display_name);
    display_chips();
    display_mult();
}

// idx_a and idx_b are assumed to be valid indexes within the hand array
// no checks will be performed here for performance's sake
void swap_cards_in_hand(int idx_a, int idx_b)
{
    CardObject* temp = hand.cards[idx_a];
    hand.cards[idx_a] = hand.cards[idx_b];
    hand.cards[idx_b] = temp;
}

static inline void sort_hand_by_suit(void)
{
    for (int idx_a = 0; idx_a < hand.hand_top; idx_a++)
    {
        for (int idx_b = idx_a + 1; idx_b <= hand.hand_top; idx_b++)
        {
            if (hand.cards[idx_a] == NULL ||
                (hand.cards[idx_b] != NULL &&
                 (hand.cards[idx_a]->card->suit > hand.cards[idx_b]->card->suit ||
                  (hand.cards[idx_a]->card->suit == hand.cards[idx_b]->card->suit &&
                   hand.cards[idx_a]->card->rank > hand.cards[idx_b]->card->rank))))
            {
                swap_cards_in_hand(idx_a, idx_b);
            }
        }
    }
}

static inline void sort_hand_by_rank(void)
{
    for (int idx_a = 0; idx_a < hand.hand_top; idx_a++)
    {
        for (int idx_b = idx_a + 1; idx_b <= hand.hand_top; idx_b++)
        {
            if (hand.cards[idx_a] == NULL ||
                (hand.cards[idx_b] != NULL &&
                 hand.cards[idx_a]->card->rank > hand.cards[idx_b]->card->rank))
            {
                swap_cards_in_hand(idx_a, idx_b);
            }
        }
    }
}

static inline bool shift_null_card_to_end(int null_card_idx)
{
    // Start by searching any non NULL cards after the NULL one
    // don't start at null_card_idx+1 to avoid potential illegal array access
    int non_null_card_idx = null_card_idx;
    for (; non_null_card_idx <= hand.hand_top; non_null_card_idx++)
    {
        if (hand.cards[non_null_card_idx] != NULL)
        {
            break;
        }
    }

    // return false if there are no non-NULL cards left/there are no more sprites to destroy
    if (non_null_card_idx > hand.hand_top)
    {
        return false;
    }

    // If there is one, shift it and all the cards that follow forward
    // This way we close the gap and ensure the next card is not NULL
    for (int j = 0; j <= hand.hand_top - non_null_card_idx; j++)
    {
        hand.cards[null_card_idx + j] = hand.cards[non_null_card_idx + j];
    }

    return true;
}

void reorder_card_sprites_layers(void)
{
    // Update the sprites in the hand by destroying them and creating new ones in the correct order
    // (This feels like a diabolical solution but like literally how else would you do this)
    for (int i = 0; i <= hand.hand_top; i++)
    {
        // a NULL card will only happen if we rearrange the sprites without having sorted them
        // before. Any NULL CardObject will be sent to the end by shifting all elements forward
        if (hand.cards[i] == NULL)
        {
            if (!shift_null_card_to_end(i))
            {
                break;
            }
        }

        // card_object_get_sprite() will not work here since we need the address
        sprite_destroy(&(hand.cards[i]->sprite_object->sprite));
    }

    // Recreate the sprites for the remaining non NULL cards, in order
    for (int i = 0; i <= hand.hand_top; i++)
    {
        if (hand.cards[i] != NULL)
        {
            // Set the sprite for the card object
            card_object_set_sprite(hand.cards[i], i);
            sprite_position(
                card_object_get_sprite(hand.cards[i]),
                fx2int(hand.cards[i]->sprite_object->x),
                fx2int(hand.cards[i]->sprite_object->y)
            );
        }
    }
}

void sort_cards(void)
{
    if (hand.sort_by_suit)
    {
        sort_hand_by_suit();
    }
    else
    {
        sort_hand_by_rank();
    }

    reorder_card_sprites_layers();
}

void hand_change_sort(bool to_sort_by_suit)
{
    if (to_sort_by_suit != hand.sort_by_suit)
    {
        hand.sort_by_suit = to_sort_by_suit;
        sort_cards();
    }
}

void hand_select_card(int index)
{
    if (index < 0 || index >= hand_nb_held_cards() || hand.state != HAND_SELECT ||
        hand.cards[index] == NULL)
        return;

    if (card_object_is_selected(hand.cards[index]))
    {
        card_object_set_selected(hand.cards[index], false);
        hand.hand_selections--;
        play_sfx(SFX_CARD_DESELECT, MM_BASE_PITCH_RATE, SFX_DEFAULT_VOLUME);
    }
    else if (hand.hand_selections < MAX_SELECTION_SIZE)
    {
        card_object_set_selected(hand.cards[index], true);
        hand.hand_selections++;
        play_sfx(SFX_CARD_SELECT, MM_BASE_PITCH_RATE, SFX_DEFAULT_VOLUME);
    }
    compute_hand_value_info();
}

void hand_deselect_all_cards(void)
{
    bool any_cards_deselected = false;
    for (int i = 0; i <= hand.hand_top; i++)
    {
        if (card_object_is_selected(hand.cards[i]))
        {
            card_object_set_selected(hand.cards[i], false);
            hand.hand_selections--;
            any_cards_deselected = true;
        }
    }

    if (any_cards_deselected)
    {
        play_sfx(SFX_CARD_DESELECT, MM_BASE_PITCH_RATE, SFX_DEFAULT_VOLUME);
    }
}

// Hand Analysis

/**
 * @brief Outputs the distribution of ranks and suits in the hand
 * @param ranks_out output - updated such as ranks_out[rank] is the number of cards of rank in the
 *                  hand. Must be of size NUM_RANKS.
 * @param suits_out output - updated such as suits_out[suit] is the number of cards if suit in the
 *                  hand Must be of size NUM_SUITS
 */
static void get_hand_distribution(u8 ranks_out[NUM_RANKS], u8 suits_out[NUM_SUITS])
{
    for (int i = 0; i < NUM_RANKS; i++)
        ranks_out[i] = 0;
    for (int i = 0; i < NUM_SUITS; i++)
        suits_out[i] = 0;

    int top = hand.hand_top;
    for (int i = 0; i <= top; i++)
    {
        if (hand.cards[i] && card_object_is_selected(hand.cards[i]))
        {
            ranks_out[hand.cards[i]->card->rank]++;
            suits_out[hand.cards[i]->card->suit]++;
        }
    }
}

/**
 * @brief Outputs the distribution of ranks and suits in the played stack
 * @param ranks_out output - updated such as ranks_out[rank] is the number of cards of rank in the
 *                  played stack. Must be of size NUM_RANKS.
 * @param suits_out output - updated such as suits_out[suit] is the number of cards if suit in the
 *                  played stack. Must be of size NUM_SUITS
 */
GBAL_UNUSED
static void get_played_distribution(u8 ranks_out[NUM_RANKS], u8 suits_out[NUM_SUITS])
{
    for (int i = 0; i < NUM_RANKS; i++)
        ranks_out[i] = 0;
    for (int i = 0; i < NUM_SUITS; i++)
        suits_out[i] = 0;

    CardObject** played = get_played_array();
    int top = get_played_top();
    for (int i = 0; i <= top; i++)
    {
        /* The difference from get_hand_distribution() (not checking if card is selected)
         * is in line Balatro behavior,
         * see https://github.com/GBALATRO/balatro-gba/issues/341#issuecomment-3691363488
         */
        if (!played[i])
            continue;
        ranks_out[played[i]->card->rank]++;
        suits_out[played[i]->card->suit]++;
    }
}

// Returns the highest N of a kind. So a full-house would return 3.
static u8 hand_contains_n_of_a_kind(u8* ranks)
{
    u8 highest_n = 0;
    for (int i = 0; i < NUM_RANKS; i++)
    {
        if (ranks[i] > highest_n)
            highest_n = ranks[i];
    }
    return highest_n;
}

static bool hand_contains_two_pair(u8* ranks)
{
    bool contains_other_pair = false;
    for (int i = 0; i < NUM_RANKS; i++)
    {
        if (ranks[i] >= 2)
        {
            if (contains_other_pair)
                return true;
            contains_other_pair = true;
        }
    }
    return false;
}

static bool hand_contains_full_house(u8* ranks)
{
    int count_three = 0;
    int count_pair = 0;
    for (int i = 0; i < NUM_RANKS; i++)
    {
        if (ranks[i] >= 3)
        {
            count_three++;
        }
        else if (ranks[i] >= 2)
        {
            count_pair++;
        }
    }
    // Full house if there is:
    // - at least one three-of-a-kind and at least one other pair,
    // - OR at least two three-of-a-kinds (second "three" acts as pair).
    // This accounts for hands with 6 or more cards even though
    // they are currently not possible and probably never will be.
    return (count_three >= 2 || (count_three && count_pair));
}

// This is mostly from Google Gemini
static bool hand_contains_straight(u8* ranks)
{
    if (!is_shortcut_joker_active())
    {
        int straight_size = get_straight_and_flush_size();
        // This is the regular case of detecting straights
        int run = 0;
        for (int i = 0; i < NUM_RANKS; ++i)
        {
            if (ranks[i])
            {
                if (++run >= straight_size)
                    return true;
            }
            else
            {
                run = 0;
            }
        }

        // Check for ace low straight
        if (straight_size >= 2 && ranks[ACE])
        {
            // With A as low, the highest rank you can use is FIVE.
            // -1 for inclusive integer distance and another -1 for the Ace e.g. need=5 -> need 2..5
            int last_needed = TWO + (straight_size - 2);
            if (last_needed <= FIVE)
            {
                bool ok = true;
                for (int r = TWO; r <= last_needed; ++r)
                {
                    if (!ranks[r])
                    {
                        ok = false;
                        break;
                    }
                }
                if (ok)
                    return true;
            }
        }

        return false;
    }
    else
    {
        // Shortcut Joker is active, we have to detect straights where any card may "skip" 1 rank
        // We do this with a dynamic programming algorithm that calculates
        // the longest possible straight that can end on each rank
        // and stopping when we find one that is {straight-size} cards long
        u8 longest_short_cut_at[NUM_RANKS] = {0};

        // A low ace can start a sequence. 'ace_low_len' is 1 if an ace is present,
        // acting as a potential predecessor for TWO and THREE.
        int ace_low_len = ranks[ACE] ? 1 : 0;

        // Iterate through all ranks from TWO up to ACE.
        for (int i = 0; i < NUM_RANKS; i++)
        {
            // No cards in this rank, no straight can end here, continue
            if (ranks[i] == 0)
            {
                longest_short_cut_at[i] = 0;
                continue;
            }

            int prev_len1 = 0;
            int prev_len2 = 0;

            // This logic handles the special connections for ace-low straights.
            if (i == TWO)
            {
                // A TWO can be preceded by a low ACE (no skip).
                prev_len1 = ace_low_len;
            }
            else if (i == THREE)
            {
                // A THREE can be preceded by a TWO (no skip) or a low ACE (skip).
                prev_len1 = longest_short_cut_at[TWO];
                prev_len2 = ace_low_len;
            }
            else if (i == ACE)
            {
                // An ACE (as the highest card) can be preceded by a KING or a QUEEN.
                prev_len1 = longest_short_cut_at[KING];
                prev_len2 = longest_short_cut_at[QUEEN];
            }
            else // For all other cards (FOUR through KING).
            {
                // A card can be preceded by the rank directly below or two ranks below.
                prev_len1 = longest_short_cut_at[i - 1];
                prev_len2 = longest_short_cut_at[i - 2];
            }

            // The length of the straight ending at rank 'i' is 1 (for the card itself)
            // plus the length of the longest valid preceding straight.
            longest_short_cut_at[i] = 1 + max(prev_len1, prev_len2);

            // If we've formed a sequence of {straight-size} or more cards, we have a straight.
            if (longest_short_cut_at[i] >= get_straight_and_flush_size())
            {
                return true;
            }
        }
    }

    return false;
}

static bool hand_contains_flush(u8* suits)
{
    for (int i = 0; i < NUM_SUITS; i++)
    {
        if (suits[i] >= get_straight_and_flush_size())
        {
            return true;
        }
    }
    return false;
}

// Returns the number of cards in the best flush found
// or 0 if no flush of min_len is found, and marks them in out_selection.
int find_flush_in_played_cards(CardObject** played, int top, int min_len, bool* out_selection)
{
    if (top < 0)
        return 0;
    for (int i = 0; i <= top; i++)
        out_selection[i] = false;

    int suit_counts[NUM_SUITS] = {0};
    for (int i = 0; i <= top; i++)
    {
        if (played[i] && played[i]->card)
        {
            suit_counts[played[i]->card->suit]++;
        }
    }

    int best_suit = -1;
    int best_count = 0;
    for (int i = 0; i < NUM_SUITS; i++)
    {
        if (suit_counts[i] > best_count)
        {
            best_count = suit_counts[i];
            best_suit = i;
        }
    }

    if (best_count >= min_len)
    {
        for (int i = 0; i <= top; i++)
        {
            if (played[i] && played[i]->card && played[i]->card->suit == best_suit)
            {
                out_selection[i] = true;
            }
        }
        return best_count;
    }
    return 0;
}

// Returns the number of cards in the best straight or 0 if no straight of min_len is found, marks
// as true them in out_selection[]. This is mostly from Google Gemini
int find_straight_in_played_cards(
    CardObject** played,
    int top,
    bool shortcut_active,
    int min_len,
    bool* out_selection
)
{
    if (top < 0)
        return 0;
    for (int i = 0; i <= top; i++)
        out_selection[i] = false;

    // --- Setup for Backtracking DP ---
    u8 longest_straight_at[NUM_RANKS] = {0};
    int parent[NUM_RANKS];
    for (int i = 0; i < NUM_RANKS; i++)
        parent[i] = -1;

    u8 ranks[NUM_RANKS] = {0};
    for (int i = 0; i <= top; i++)
    {
        if (played[i] && played[i]->card)
        {
            ranks[played[i]->card->rank]++;
        }
    }

    // --- Run DP to find longest straight ---
    // This is nearly identical to hand_contains_straight() logic
    // TODO: Consolidate functions to avoid code duplication?
    // Might cost performance because this does a little more
    int ace_low_len = ranks[ACE] ? 1 : 0;
    for (int i = 0; i < NUM_RANKS; i++)
    {
        if (ranks[i] > 0)
        {
            int prev1 = 0, prev2 = 0;
            int parent1 = -1, parent2 = -1;

            if (shortcut_active)
            {
                if (i == TWO)
                {
                    prev1 = ace_low_len;
                    parent1 = ACE;
                }
                else if (i == THREE)
                {
                    prev1 = longest_straight_at[TWO];
                    parent1 = TWO;
                    prev2 = ace_low_len;
                    parent2 = ACE;
                }
                else if (i == ACE)
                {
                    prev1 = longest_straight_at[KING];
                    parent1 = KING;
                    prev2 = longest_straight_at[QUEEN];
                    parent2 = QUEEN;
                }
                else
                {
                    prev1 = longest_straight_at[i - 1];
                    parent1 = i - 1;
                    if (i > 1)
                    {
                        prev2 = longest_straight_at[i - 2];
                        parent2 = i - 2;
                    }
                }
            }
            else
            {
                if (i == TWO)
                {
                    prev1 = ace_low_len;
                    parent1 = ACE;
                }
                else if (i == ACE)
                {
                    prev1 = longest_straight_at[KING];
                    parent1 = KING;
                }
                else
                {
                    prev1 = longest_straight_at[i - 1];
                    parent1 = i - 1;
                }
            }

            // Parallels longest_short_cut_at[i] = 1 + max(prev_len1, prev_len2);
            // in hand_contains_straight()
            if (prev1 >= prev2)
            {
                longest_straight_at[i] = 1 + prev1;
                parent[i] = parent1;
            }
            else
            {
                longest_straight_at[i] = 1 + prev2;
                parent[i] = parent2;
            }
        }
    }

    // --- Find best straight and backtrack ---
    int best_len = 0;
    int end_rank = -1;
    for (int i = 0; i < NUM_RANKS; i++)
    {
        if (longest_straight_at[i] >= best_len)
        {
            best_len = longest_straight_at[i];
            end_rank = i;
        }
    }

    if (best_len >= min_len)
    {
        u8 needed_ranks[NUM_RANKS] = {0};
        int current_rank = end_rank;
        while (current_rank != -1 && best_len > 0)
        {
            needed_ranks[current_rank]++;
            current_rank = parent[current_rank];
            best_len--;
        }

        for (int i = 0; i <= top; i++)
        {
            if (played[i] && played[i]->card && needed_ranks[played[i]->card->rank] > 0)
            {
                out_selection[i] = true;
                needed_ranks[played[i]->card->rank]--;
            }
        }

        int final_card_count = 0;
        for (int i = 0; i <= top; i++)
        {
            if (out_selection[i])
                final_card_count++;
        }
        return final_card_count;
    }
    return 0;
}

// This is used for the special case in "Four Fingers" where you can add a pair into a straight
// (e.g. AA234 should score all 5 cards)
void select_paired_cards_in_hand(CardObject** played, int played_top, bool* selection)
{
    // Build a set of ranks that are already selected
    bool rank_selected[NUM_RANKS] = {0};
    bool any_selected_rank = false;

    for (int i = 0; i <= played_top; i++)
    {
        if (selection[i] && played[i] && played[i]->card)
        {
            rank_selected[played[i]->card->rank] = true;
            any_selected_rank = true;
        }
    }

    // If no ranks were selected initially, nothing to do
    if (!any_selected_rank)
        return;

    // Add any unselected card to the selection if if shares a rank with the selected ranks
    for (int i = 0; i <= played_top; i++)
    {
        if (played[i] && played[i]->card && !selection[i])
        {
            if (rank_selected[played[i]->card->rank])
            {
                selection[i] = true;
            }
        }
    }
}

static ContainedHandTypes compute_contained_hand_types(void)
{
    ContainedHandTypes hand_types = {0};

    // Idk if this is how Balatro does it but this is how I'm doing it
    if (hand.hand_selections == 0 || hand.state == HAND_DISCARD)
    {
        return hand_types;
    }

    hand_types.HIGH_CARD = 1;

    u8 suits[NUM_SUITS];
    u8 ranks[NUM_RANKS];
    get_hand_distribution(ranks, suits);

    // The following can be optimized better but not sure how much it matters
    u8 n_of_a_kind = hand_contains_n_of_a_kind(ranks);

    // Pair and 2 Pair
    if (n_of_a_kind >= 2)
    {
        hand_types.PAIR = 1;

        if (hand_contains_two_pair(ranks))
        {
            hand_types.TWO_PAIR = 1;
        }
    }

    // 3 OAK
    if (n_of_a_kind >= 3)
    {
        hand_types.THREE_OF_A_KIND = 1;
    }

    // Straight
    if (hand_contains_straight(ranks))
    {
        hand_types.STRAIGHT = 1;
    }

    // Flush
    if (hand_contains_flush(suits))
    {
        hand_types.FLUSH = 1;
    }

    // Full House
    if (n_of_a_kind >= 3 && hand_contains_full_house(ranks))
    {
        hand_types.FULL_HOUSE = 1;
    }

    // 4 OAK
    if (n_of_a_kind >= 4)
    {
        hand_types.FOUR_OF_A_KIND = 1;
    }

    // Straight Flush
    if (hand_types.STRAIGHT && hand_types.FLUSH)
    {
        hand_types.STRAIGHT_FLUSH = 1;
    }

    // Royal Flush
    if (hand_types.STRAIGHT_FLUSH)
    {
        if (ranks[TEN] && ranks[JACK] && ranks[QUEEN] && ranks[KING] && ranks[ACE])
        {
            hand_types.ROYAL_FLUSH = 1;
        }
    }

    // 5 OAK
    if (n_of_a_kind >= 5)
    {
        hand_types.FIVE_OF_A_KIND = 1;
    }

    // Flush House and Five
    if (hand_types.FLUSH)
    {
        if (hand_types.FULL_HOUSE)
        {
            hand_types.FLUSH_HOUSE = 1;
        }

        if (hand_types.FIVE_OF_A_KIND)
        {
            hand_types.FLUSH_FIVE = 1;
        }
    }

    return hand_types;
}

static enum HandType compute_hand_type(struct ContainedHandTypes contained_types)
{
    enum HandType ret;

    // test each pit see if it's set to 1, and return the first one
    for (ret = FLUSH_FIVE; ret > NONE; ret--)
    {
        // Shift the bit we want to check to the front and mask it with 1 to keep only that
        // Since the ContainedHandTypes is ordered the same way as the HandType enum, we
        // can shift right by ret-1 to have the bit we want at the front
        if ((contained_types.value >> (ret - 1)) & 0x1)
        {
            break;
        }
    }

    // If we broke early, ret contains the value of the HandType enum corresponding to
    // the position of the highest bit set to 1 in contained_types.value, which is the
    // most powerful poker hand contained in the current Hand
    // If not, then it contains NONE, which is what we're supposed to return when there
    // are no Hands contained in what we played
    return ret;
}
