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

    valves: [..]Valve;    
    valve_table: Table(u16, s64);


    for line: lines {
        name := slice(line, 6, 2);
        valve_id := (cast(u16)name[0]) << 8 | name[1];

        valve_index, found := table_find(*valve_table, valve_id);

        if !found {
            valve_index = add_valve(valve_id, *valves);
            table_set(*valve_table, valve_id, valve_index);
        }

        equals_index := find_index_from_left(line, cast(u8) #char "=",);
        semicolon_index := find_index_from_left(line, cast(u8) #char ";");

        valves[valve_index].flow = string_to_int(slice(line, equals_index + 1, semicolon_index - equals_index - 1), 10, s64);

        valve_split := split(line, "valve");
        space_split := split(valve_split[1], cast(u8) #char " ");

        for space_split {
            if it.count != 2 && it.count != 3  continue;
            connected_valve_id := (cast(u16)it[0]) << 8 | it[1];

            connected_valve_index, connected_found := table_find(*valve_table, connected_valve_id);
            connected_valve: *Valve;

            if connected_found {
                connected_valve = *valves[connected_valve_index];
            } else {
                connected_valve_index = add_valve(connected_valve_id, *valves);
                connected_valve = *valves[connected_valve_index];

                table_set(*valve_table, connected_valve_id, connected_valve_index);
            }

            table_set(*valves[valve_index].connections, connected_valve_id, connected_valve_index);
        }

    }

    assert(valve_table.count == lines.count);
    aa_index, ff := table_find(*valve_table, (cast(u16)65) << 8 | 65);

    for *valve_1, valve_1_index: valves {
        if valve_1.flow == 0 && valve_1_index != aa_index continue;
        for valve_2_index: valve_1_index + 1..valves.count - 1 {
            valve_2 := *valves[valve_2_index];
            
            if valve_2.flow == 0 continue;

            distance := find_shortest_route(valve_1_index, valve_2_index, valves);

            array_add(*valve_1.compressed, .{ valve_2_index, distance });
            array_add(*valve_2.compressed, .{ valve_1_index, distance });
        }
    }

    print("Part 1: %\n", find_best_pressure_relief(valves, 30, 0, aa_index));

    part2 := 0;

    first_run, first_visited := find_best_pressure_relief(valves, 26, 0, aa_index);
    second_run := find_best_pressure_relief(valves, 26, first_visited, aa_index);

    print("Part 2: %,  %    %\n", first_run + second_run, first_run, second_run);

}

find_best_pressure_relief :: (valves: [..]Valve, time_left: s64, already_opened: u64, start_index: s64) -> s64, u64 {
    deque: Deque(struct { index: s64; time_left: s64; visited: u64; pressure_relief: s64; history: [..]History; });
    resize(*deque, 400_000);
    defer deinit(*deque);


    pressure_max := 0;
    visited: u64  = 0;

    start: [..]History;

    deque_add_last(*deque, .{ start_index, time_left, (cast(u64)1) << start_index, 0, start });

    best_fl := 0;

    while deque.count > 0 {
        item := deque_remove_first(*deque);

        ff := item.history.count > 7 && cast(string) to_name(valves[item.history[6].index].name) == "FL" && cast(string) to_name(valves[item.history[7].index].name) == "EG";

        for valves[item.index].compressed {
            if item.visited & ((cast(u64)1) << it.valve_index) != 0  continue;

            if item.time_left >= 0 {
                visited_now := item.visited | ((cast(u64)1) << it.valve_index);

                time_left_now := item.time_left - 1 - it.time;
                pressure_relief := item.pressure_relief;

                if already_opened & ((cast(u64)1) << it.valve_index) == 0  pressure_relief += valves[it.valve_index].flow * time_left_now;

                duped: [..]History;
                array_copy(*duped, item.history);
                array_add(*duped, .{ it.valve_index, 1 });

                if pressure_max < pressure_relief {
                    pressure_max = pressure_relief;
                    visited = visited_now;
                }
                deque_add_last(*deque, .{ it.valve_index, time_left_now, visited_now, pressure_relief, duped });
            }
        }
    }

    return pressure_max, visited;
}

print_history :: (history: [..]History, valves: [..]Valve) {
    for history {
        print("%, ", cast(string) to_name(valves[it.index].name));
    }
    print("\n");
}

add_valve :: (valve_id: u16, valves: *[..]Valve) -> s64 {
    valve_index := valves.count;
    valve := array_add(valves);
    valve.name = valve_id;

    return valve_index;
}

to_name :: (valve_id: u16) -> [2]u8 {
    name: [2]u8;
    name[0] = xx (valve_id >> 8);
    name[1] = xx (valve_id & 0xFF);
    return name;
}

print_visited :: (valves: [..]Valve, visited: u64) {
    for 0..63 {
        if 1 & (visited >> it) == 1 {
            print("%, ", cast(string) to_name(valves[it].name));
        }
    }
}

find_shortest_route :: (from: s64, to: s64, valves: [..]Valve) -> s64 {
    deque: Priority_Queue(Q_Item, (a, b) => a.steps - b.steps);
    defer deinit(*deque);

    start: [..]History;
    visited: Table(s64, s64);

    add(*deque, .{ from, 0, start });

    while deque.count > 0 {
        removed, item := remove_top(*deque);
        valve := valves[item.index];

        for v,k: valve.connections {
            if v == to {
                return item.steps + 1;
            }

            visited_index := (cast(u64)1) << cast(u64)v;

            if table_contains(*visited, v)  continue;
            table_set(*visited, v, v);

            duped: [..]History;
            array_copy(*duped, item.history);
            array_add(*duped, .{ v, item.steps + 1 });


            add(*deque, .{ v, item.steps + 1, duped });
        }
    }

    return -1;
}

#scope_file

Valve :: struct {
    name: u16;
    flow: s64;
    connections: Table(u16, s64);
    compressed: [..]struct { valve_index: s64; time: s64; };
}

History :: struct {
    index: s64;
    t: s64;
}

Q_Item :: struct {
    index: s64;
    steps: s64;
    history: [..]History;
}