#import "String";
#import "Sort";
#import "Basic";
solve_day11 :: (test: bool) {
    contents := read_entire_file(ifx test then "inputs/day11_test.txt" else "inputs/day11.txt");
    sections := split(contents, "\n\n");

    part1 := solve(true, sections);
    print("Part 1: %\n", part1);
    part2 := solve(false, sections);

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

solve :: (part1: bool, sections: []string) -> s64 {
    monkeys: [..]Monkey;

    for section: sections {
        section_lines := split(section, "\n");
        starting_items_split := split(slice(section_lines[1], 18, section_lines[1].count - 18), ", ");

        operation_number := ifx ends_with(section_lines[2], "old") then -1 else string_to_int(slice(section_lines[2], 24, section_lines[2].count - 24), 10, s16);

        new_monkey := array_add(*monkeys);
        << new_monkey = .{
            operation = section_lines[2][23],
            operation_number = to_s128(operation_number),
            test = string_to_int(slice(section_lines[3], 20, section_lines[3].count - 20), 10, s64),
            test_true = string_to_int(slice(section_lines[4], 28, section_lines[4].count - 28), 10, s64),
            test_false = string_to_int(slice(section_lines[5], 29, section_lines[5].count - 29), 10, s64),
        };

        for starting_items_split {
            array_add(*new_monkey.items, to_s128(string_to_int(it, 10, s64)));
        }

    }

    minus_one := to_s128(-1);
    zero := to_s128(0);

    modulus := 3;

    if !part1 {
        modulus = 1;
        for monkeys {
            modulus *= it.test;
        }
    }

    run_to := ifx part1 then 20 else 10_000;

    for 1..run_to {

        for *monkey, monkey_id: monkeys {
            for *monkey.items {
                monkey.handled += 1;
                op_num := ifx monkey.operation_number != minus_one then monkey.operation_number else << it;

                old_val := << it;
                
                if monkey.operation == {
                    case #char "+";
                        << it = op_num + << it;
                    case #char "*";
                        << it= << it * op_num;
                    case;
                        assert(false, "Unknown operation: %", monkey.operation);
                }

                assert(old_val <= << it, "Wrapping? %, %", old_val, << it);

                f := u8.[monkey.operation];

                res, remainder := signed_divide_with_remainder(<< it, to_s128(modulus));

                << it = ifx part1 then << it / modulus else remainder;

                give_to := -1;

                res, remainder = signed_divide_with_remainder(<< it, to_s128(monkey.test));

                if remainder == zero { 
                    give_to = monkey.test_true;
                } else {
                    give_to = monkey.test_false;
                }
                
                item := << it;
                remove it;
                array_add(*monkeys[give_to].items, item);
            }
        }
    }

    quick_sort(monkeys, (a, b) => b.handled - a.handled);

    return monkeys[0].handled * monkeys[1].handled;
}

#scope_file
Monkey :: struct {
    items: [..]S128;
    operation: u8;
    operation_number: S128;
    test: s64;
    test_true: s64;
    test_false: s64;
    handled: s64;
}