solve_day21 :: (test: bool) {
    contents := read_entire_file(ifx test then "inputs/day21_test.txt" else "inputs/day21.txt");
    lines    := split(contents, cast(u8) #char "\n");

    monkeys: [..]Monkey;

    monkey_index: Table(string, s64);
    root_monkey_index, found      := -1, false;
    humn_monkey_index, found_humn := -1, false;

    for line: lines {
        parts := split(line, cast(u8) #char " ");
        name  := slice(parts[0], 0, parts[0].count - 1);

        if name == "humn" {
            found_humn = true;
            humn_monkey_index = monkeys.count;
        } else if name == "root" {
            found = true;
            root_monkey_index = monkeys.count;
        }

        table_set(*monkey_index, name, monkeys.count);
        if parts.count > 2 {
            array_add(*monkeys, .{
                name = name,
                calculated = false,
                operation = convert_to_monkey_operation(parts[2]),
                depends_on = string.[parts[1], parts[3]],
            });
        } else {
            array_add(*monkeys, .{
                name = name,
                calculated = true,
                operation = .NONE,
                value = string_to_int(parts[1], 10, s64),
            });
        }
    }

    part1 := calc_value(*monkeys[root_monkey_index], monkeys, monkey_index);
    part2 := 0;

    monkey_a_index,_ := table_find(*monkey_index, monkeys[root_monkey_index].depends_on[0]);
    monkey_b_index,_ := table_find(*monkey_index, monkeys[root_monkey_index].depends_on[1]);
    reset_monkeys(monkeys);

    monkey_a := *monkeys[monkey_a_index];
    monkey_b := *monkeys[monkey_b_index];

    value_a, has_humn_a := calc_value(monkey_a, monkeys, monkey_index);
    value_b, has_humn_b := calc_value(monkey_b, monkeys, monkey_index);

    if has_humn_a {
        part2 = cast,no_check(s64) find_humn_value(monkey_a, value_b, true, monkeys, monkey_index);
    } else if has_humn_b {
        part2 = cast,no_check(s64) find_humn_value(monkey_b, value_a, false, monkeys, monkey_index);
    } else {
        assert(false, "Neither has humn?");
    }

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

#scope_file
Monkey :: struct {
    name: string;
    value: s64;
    calculated: bool;
    operation: Monkey_Operation = .NONE;
    depends_on: [2]string;
}

reset_monkeys :: (monkeys: [..]Monkey) {
    for *monkeys {
        if it.operation != .NONE {
            it.calculated = false;
        }
    }
}

find_humn_value :: (monkey: Monkey, value: $T, right: bool, monkeys: [..]Monkey, monkey_index: Table) -> T {

    if monkey.name == "humn"  return value;

    monkey_a_index,fa := table_find(*monkey_index, monkey.depends_on[0]);
    monkey_b_index,fb := table_find(*monkey_index, monkey.depends_on[1]);
    monkey_a := *monkeys[monkey_a_index];
    monkey_b := *monkeys[monkey_b_index];

    assert(fa, "Failed to find '%', %", monkey.depends_on[0], monkey.name);
    assert(fb, "Failed to find '%', %", monkey.depends_on[1], monkey.name);

    value_a, has_humn_a := calc_value(monkey_a, monkeys, monkey_index);
    value_b, has_humn_b := calc_value(monkey_b, monkeys, monkey_index);

    if has_humn_a {

        rev := reverse_operation(monkey.operation, true, value, value_b, monkey.name);
        return find_humn_value(monkey_a, rev, true, monkeys, monkey_index);
    } else if has_humn_b {
        rev := reverse_operation(monkey.operation, false, value, value_a, monkey.name);
        return find_humn_value(monkey_b, rev, false, monkeys, monkey_index);
    } else {
        assert(false, "Neither has humn?");
    }

    return 0;

}

calc_value :: (monkey: *Monkey, monkeys: [..]Monkey, monkey_index: Table) -> s64, bool {
    if monkey.operation == .NONE return monkey.value, monkey.name == "humn";

    monkey_a_index, found_a := table_find(*monkey_index, monkey.depends_on[0]);
    monkey_b_index, found_b := table_find(*monkey_index, monkey.depends_on[1]);

    assert(found_a, "Failed to find monkey '%',   %", monkey.depends_on[0], monkey.name);
    assert(found_b, "Failed to find monkey '%'    %", monkey.depends_on[1], monkey.name);

    monkey_a_value, has_a_humn := calc_value(*monkeys[monkey_a_index], monkeys, monkey_index);
    monkey_b_value, has_b_humn := calc_value(*monkeys[monkey_b_index], monkeys, monkey_index);

    result := perform_operation(monkey.operation, monkey_a_value, monkey_b_value);

    monkey.value = result;
    monkey.calculated = true;

    return result, has_a_humn || has_b_humn;
}

perform_operation :: (operation: Monkey_Operation, a: $T, b: T) -> T {
    if operation == {
        case .NONE; assert(false, "Got .NONE operation..");
        case .MULT; return a * b;
        case .DIV; return a / b;
        case .PLUS; return a + b;
        case .MINUS; return a - b;
    }
    return -1;
}

reverse_operation :: (operation: Monkey_Operation, right: bool, a: $T, b: T, name: string) -> T {
    if operation == {
        case .NONE; assert(false, "Got .NONE operation..");
        case .MULT; return a / b;
        case .DIV; return a * b;
        case .PLUS; return a - b;
        case .MINUS; if right then return a + b; else return b - a;
    }

    return 0;
}

convert_to_monkey_operation :: (operation: string) -> Monkey_Operation {
    if operation == {
        case "+";
            return .PLUS;
        case "-";
            return .MINUS;
        case "/";
            return .DIV;
        case "*";
            return .MULT;
        case;
            assert(false, "Bad operation %", operation);
    }
    return .NONE;
}

Monkey_Operation :: enum {
    NONE :: -1;
    PLUS :: 0;
    MINUS :: 1;
    MULT :: 2;
    DIV :: 3;
}