#import "File";
#import "Basic";
#import "String";

ONE  :: #run to_u128(cast(u8)1);
ZERO :: #run to_u128(cast(u8)0);

solve_day4 :: (test: bool) {
    contents := read_entire_file(ifx test then "inputs/day4_test.txt" else "inputs/day4.txt");
    sections := split(contents, "\n\n");

    draws := parse_draws(sections[0]);
    defer array_free(draws);

    bingo_cards: [..]Bingo_Card;

    for 1..sections.count - 1 {
        array_add(*bingo_cards, parse_bingo_card(sections[it]));
    }

    part1 := 0;
    part2 := 0;

    cards_won := 0;

    drawn := ZERO;
    for draw, d_i: draws {
        drawn = drawn | (ONE << draw);

        for *card: bingo_cards {
            if !card.won && has_won(drawn, card) {
                part2 = xx (draw * sum_of_marked(drawn, card));
                if part1 == 0 {
                    part1 = part2;
                }

                card.won = true;

            }
        }
    }
    print("Part 1: %\n", part1);
    print("Part 2: %\n", part2);
}

#scope_file

has_won :: (drawn: U128, card: Bingo_Card) -> bool {
    for row: card.rows {
        if (row & drawn) == row  return true;
    }
    for column: card.columns {
        if (column & drawn) == column return true;
    }

    return false;
}

sum_of_marked :: (drawn: U128, card: Bingo_Card) -> u64 {
    f := ZERO;

    result: u64 = 0;

    for row: card.rows {
        f = f | ((~drawn) & row);
    }

    for 0..127 {
        if (f >> xx it) & ONE == ONE {
            result += xx it;
        }
    }

    return result;
}

parse_draws :: (line: string) -> []u8 {

    numbers := split(line, ",");

    result := NewArray(numbers.count, u8);

    for numbers {
        result[it_index] = string_to_int(it, 10, u8);
    }

    return result;
}

parse_bingo_card :: (card_section: string) -> Bingo_Card {
    lines := split(card_section, "\n");

    card: Bingo_Card;

    for line, line_i: lines {
        numbers := split(line, " ");
        
        number_i := 0;
        for number: numbers {
            if number.count > 0 {
                defer number_i += 1;
                n := string_to_int(number, 10, u8);
                number_bit := ONE << n;
                card.rows[line_i] = card.rows[line_i] | number_bit;
                card.columns[number_i] = card.columns[number_i] | number_bit;
            }
        }
    }

    return card;
}

Bingo_Card :: struct {
    rows: [5]U128;
    columns: [5]U128;
    won: bool = false;
}

operator | :: (a: U128, b: U128) -> U128 {
    return .{
        low = a.low | b.low,
        high = a.high | b.high,
    };
}

operator & :: (a: U128, b: U128) -> U128 {
    return .{
        low = a.low & b.low,
        high = a.high & b.high,
    };
}

operator ~ :: (a: U128) -> U128 {
    return .{ high = ~a.high, low = ~a.low };
}