const std = @import("std");
const alloc = std.heap.page_allocator;
const List = std.ArrayList;
const OccuranceMap = std.AutoHashMap(Card, u8);

pub const Hand = struct {
    cards: [5]Card,
    bid: u64,
    type: HandType,
};

pub const HandType = enum {
    Five,
    Four,
    FullHouse,
    Three,
    TwoPair,
    OnePair,
    HighCard,

    pub fn as_u8(self: HandType) u8 {
        return switch (self) {
            .Five => 7,
            .Four => 6,
            .FullHouse => 5,
            .Three => 4,
            .TwoPair => 3,
            .OnePair => 2,
            .HighCard => 1,
        };
    }
};

pub const Card = u8;

pub fn parse(data: []const u8) !List(Hand) {
    var lines = std.mem.tokenizeScalar(u8, data, '\n');

    var hands = List(Hand).init(alloc);

    while (lines.next()) |line| {
        var line_parts = std.mem.tokenizeScalar(u8, line, ' ');

        const cards_part = line_parts.next().?;

        var cards: [5]Card = undefined;
        var occurances = OccuranceMap.init(alloc);
        defer occurances.deinit();

        for (cards_part, 0..) |card, i| {
            const card_num = switch (card) {
                'A' => 14,
                'K' => 13,
                'Q' => 12,
                'J' => 11,
                'T' => 10,
                '2'...'9' => card - 48,
                else => unreachable,
            };
            cards[i] = card_num;

            if (occurances.get(card_num)) |val| {
                try occurances.put(card_num, val + 1);
            } else {
                try occurances.put(card_num, 1);
            }
        }

        const hand_type = determine_hand_type(&occurances);

        const bid_part = line_parts.next().?;
        const bid = try std.fmt.parseInt(u64, bid_part, 10);

        const hand = .{
            .cards = cards,
            .bid = bid,
            .type = hand_type,
        };

        try hands.append(hand);
    }

    return hands;
}

fn determine_hand_type(occurances: *const OccuranceMap) HandType {
    switch (occurances.count()) {
        1 => return HandType.Five,
        2 => {
            var iter = occurances.valueIterator();
            while (iter.next()) |val| {
                switch (val.*) {
                    1, 4 => return HandType.Four,
                    else => return HandType.FullHouse,
                }
            }
        },
        3 => {
            var iter = occurances.valueIterator();
            while (iter.next()) |val| {
                switch (val.*) {
                    3 => return HandType.Three,
                    2 => return HandType.TwoPair,
                    else => continue,
                }
                unreachable;
            }
        },
        4 => return HandType.OnePair,
        5 => return HandType.HighCard,
        else => unreachable,
    }

    unreachable;
}