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

    valley := parse_valley(lines);

    start := Vec2.{ x = 0, y = -1 };
    end   := Vec2.{ x = valley.boundry.x - 1, y = valley.boundry.y };

    part1 := find_exit(valley, start, end);
    part2 := find_exit(valley, end, start);
    part2 += find_exit(valley, start, end);
    part2 += part1;

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

}

#scope_file
Valley :: struct {
    blizzards: [..]Blizzard;
    boundry: Vec2;
}

parse_valley :: (lines: []string) -> Valley {
    valley: Valley;
    valley.boundry.x = lines[0].count - 2;
    valley.boundry.y = lines.count - 2;

    for line, y: lines {
        for 0..line.count - 1 {
            if line[it] == {
                case #char "v"; array_add(*valley.blizzards, .{ pos = .{ x = it - 1, y = y - 1 }, dir = .{ x =  0, y =  1 }});
                case #char ">"; array_add(*valley.blizzards, .{ pos = .{ x = it - 1, y = y - 1 }, dir = .{ x =  1, y =  0 }});
                case #char "<"; array_add(*valley.blizzards, .{ pos = .{ x = it - 1, y = y - 1 }, dir = .{ x = -1, y =  0 }});
                case #char "^"; array_add(*valley.blizzards, .{ pos = .{ x = it - 1, y = y - 1 }, dir = .{ x =  0, y = -1 }});
            }
        }
    }

    return valley;
}

tick :: (valley: Valley) {
    for *valley.blizzards {
        it.pos += it.dir;

        it.pos.x = (it.pos.x + valley.boundry.x) % valley.boundry.x;
        it.pos.y = (it.pos.y + valley.boundry.y) % valley.boundry.y;
    }
}

is_safe :: (valley: Valley, pos: Vec2) -> bool {
    for valley.blizzards {
        if pos.x == it.pos.x && pos.y == it.pos.y  return false;
    }
    return true;
}

is_inside :: (valley: Valley, pos: Vec2) -> bool {
    if (pos.x == 0 && pos.y == -1) || (pos.x == valley.boundry.x - 1 && pos.y == valley.boundry.y)  return true;

    return pos.x >= 0 && pos.y >= 0 && pos.x < valley.boundry.x && pos.y < valley.boundry.y;
    
}

find_exit :: (valley: Valley, start: Vec2, exit: Vec2) -> s64 {
    now_t: Table(u64, Vec2);
    next_t: Table(u64, Vec2);

    defer deinit(*now_t);
    defer deinit(*next_t);

    table_set(*now_t, convert(start), start);

    steps := 0;

    while true {
        steps += 1;
        tick(valley);

        for n, k: now_t {
            for dir: Vec2.[.{1,0},.{-1,0},.{0,1},.{0,-1},.{0,0}] {
                next_pos := n + dir;
                if is_inside(valley, next_pos) && is_safe(valley, next_pos) {
                    if next_pos.x == exit.x && next_pos.y == exit.y  return steps;

                    table_set(*next_t, convert(next_pos), next_pos);
                }
            }
        }

        now_t, next_t = next_t, now_t;
        table_reset(*next_t);

        assert(now_t.count > 0);

        print("%\n", now_t.count);
    }
    return -1;
}

Blizzard :: struct {
    dir: Vec2;
    pos: Vec2;
}

Vec2 :: struct {
    x: s64;
    y: s64;
}

operator + :: (v1: Vec2, v2: Vec2) -> Vec2 {
    return .{ x = v1.x + v2.x, y = v1.y + v2.y };
}

convert :: (v: Vec2) -> u64 {
    return ((cast(u64)v.x) << 32) | (cast(u64)v.y);
}