solve_day22 :: (test: bool) {
    contents := read_entire_file(ifx test then "inputs/day22_test2.txt" else "inputs/day22.txt");
    sections := split(contents, "\n\n");

    instruction_section := sections[1];
    
    map_lines := split(sections[0], cast(u8) #char "\n");

    map_builder: String_Builder;

    map_width := map_lines[0].count;
    map_height := map_lines.count;

    for map_lines  map_width = max(map_width, it.count);

    faces_x  := ifx map_width > map_height then 4 else 3;
    faces_y  := 7 - faces_x;
    face_dim := map_lines.count / faces_y;

    empty_string := NewArray(map_width, u8);
    memset(empty_string.data, cast(u8) #char " ", empty_string.count);

    for map_lines {
        print_to_builder(*map_builder, it);

        if it.count < map_width {
            print_to_builder(*map_builder, xx array_view(empty_string, 0, map_width - it.count));
        }
    }

    map := builder_to_string(*map_builder);
    defer free(map.data);
    map2 := copy_string(map);
    defer free(map2.data);

    instructions := parse_instructions(instruction_section);

    pos := Vec2.{
        x = 0,
        y = 0,
    };

    dir := Vec2.{
        x = 1,
        y = 0,
    };

    while map[pos.x] == #char " "  pos.x += face_dim;

    for instructions {
        if it.type == .TURN {
            dir = turn(dir, it.value);
        } else {
            for 0..it.value - 1 {
                new_pos := pos + dir;

                if new_pos.x >= map_width || new_pos.x < 0 {
                    new_pos.x += -dir.x * map_width;
                } else     if new_pos.y >= map_height || new_pos.y < 0 {
                    new_pos.y += -dir.y * map_height;
                } else if map[new_pos.y * map_width + new_pos.x] == #char " " {
                    if dir.x == 1 {
                        new_pos.x = 0;
                    } else if dir.x == -1 {
                        new_pos.x = map_width - 1;
                    } else if dir.y == 1 {
                        new_pos.y = 0;
                    } else if dir.y == -1 {
                        new_pos.y = map_height - 1;
                    } else assert(false, "dir has bad values %", dir);
                }

                while map[new_pos.y * map_width + new_pos.x] == #char " "  new_pos += dir;

                //map[pos.y * map_width + pos.x] = convert_dir_to_char(dir);
                if map[new_pos.y * map_width + new_pos.x] != #char "#" {
                    pos = new_pos;
                } else {
                    continue;
                }
            }
        }
    }
    map[pos.y * map_width + pos.x] = #char "x";

    for 0..map_height - 1 {
        print("%\n", slice(map, it * map_width, map_width));
    }

    pos += Vec2.{1,1};

    print("Part 1: %\n", pos.y * 1000 + pos.x * 4 + convert_dir_to_val(dir));

    cube_faces: [6]Cube_Face;

    face_map: [12]s64;
    memset(face_map.data, 255, size_of(s64) * face_map.count);

    assert(face_dim == map_lines.count / faces_y, "Face dim does not line up: % != %", face_dim, map_lines.count / faces_y);
    assert(map_lines[0].count % face_dim == 0, "Width (%) is not divisible by %", face_dim, map_lines[0].count);
    assert(map_lines.count % face_dim == 0, "Height (%) is not divisible by %", face_dim, map_lines.count);

    {
        face_num := 0;
        
        for y: 0..faces_y - 1 {
            for x: 0..faces_x - 1 {
                if map2[y * face_dim * map_width + x * face_dim] != #char " " {
                    cube_faces[face_num].on_map.x = face_dim * x;
                    cube_faces[face_num].on_map.y = face_dim * y;
                    face_map[y * faces_x + x] = face_num;
                    face_num += 1;
                }
            }
        }
        assert(face_num == 6, "Did not find enough faces %.", face_num);
    }
    stich_cube(cube_faces, face_map, faces_x);

    pos = .{0,0};
    dir = .{1,0};
    current_face := 0;


    for instructions {
        if it.type == .TURN {
            dir = turn(dir, it.value);
        } else {
            for 0..it.value - 1 {
                new_pos := pos + dir;
                new_dir := dir;
                new_current_face := current_face;

                if new_pos.x < 0 {
                    new_pos.x = face_dim - 1;
                    left_dir := cube_faces[current_face].neighbours[3].dir;
                    new_pos = convert_coords(new_pos, left_dir, face_dim);
                    new_dir = convert_dir(dir, left_dir);
                    new_current_face = cube_faces[current_face].neighbours[3].face;
                } else if new_pos.x >= face_dim {
                    new_pos.x = 0;
                    right_dir := cube_faces[current_face].neighbours[1].dir;
                    new_pos = convert_coords(new_pos, right_dir, face_dim);
                    new_dir = convert_dir(dir, right_dir);
                    new_current_face = cube_faces[current_face].neighbours[1].face;
                } else if new_pos.y < 0 {
                    new_pos.y = face_dim - 1;
                    up_dir := cube_faces[current_face].neighbours[0].dir;
                    new_pos = convert_coords(new_pos, up_dir, face_dim);
                    new_dir = convert_dir(dir, up_dir);
                    new_current_face = cube_faces[current_face].neighbours[0].face;
                } else if new_pos.y >= face_dim {
                    new_pos.y = 0;
                    bottom_dir := cube_faces[current_face].neighbours[2].dir;
                    new_pos = convert_coords(new_pos, bottom_dir, face_dim);
                    new_dir = convert_dir(dir, bottom_dir);
                    print("----> %, %\n", dir, new_dir);
                    new_current_face = cube_faces[current_face].neighbours[2].face;
                }

                map_pos := Vec2.{
                    x = new_pos.x + cube_faces[new_current_face].on_map.x,
                    y = new_pos.y + cube_faces[new_current_face].on_map.y,
                };
                p := Vec2.{
                    x = pos.x + cube_faces[current_face].on_map.x,
                    y = pos.y + cube_faces[current_face].on_map.y,
                };

                if map2[map_pos.y * map_width + map_pos.x] != #char "#" {
                    map2[p.y * map_width + p.x] = convert_dir_to_char(dir);
                    pos = new_pos;
                    dir = new_dir;
                    current_face = new_current_face;

                } else {
                    continue;
                }
            }
        }

    }

    final_pos := Vec2.{
        x = pos.x + cube_faces[current_face].on_map.x,
        y = pos.y + cube_faces[current_face].on_map.y,
    };

    map2[final_pos.y * map_width + final_pos.x] = convert_dir_to_char(dir);

    for 0..map_height - 1 {
        print("%\n", slice(map2, it * map_width, map_width));
    }
    final_pos += Vec2.{1,1};

    print("Part 2: %\n", final_pos.y * 1000 + final_pos.x * 4 + convert_dir_to_val(dir));
    
    t: [1]u8;

    for 1..255 {
        t[0] = xx it;
        print("% ", cast(string) t);
    }
    print("\n");
}

convert_coords :: (v: Vec2, dir: Dir, map_dim: s64) -> Vec2 {
    if dir == {
        case .UP; return v;
        case .LEFT; return .{ x = v.y, y = map_dim - 1 - v.x };
        case .DOWN; return .{ x = map_dim - 1 - v.x, y = map_dim - 1 - v.y };
        case .RIGHT; return .{ x = map_dim - 1 - v.y, y = v.x };
    }

    assert(false, "Bad dir %", dir);

    return v;
}

convert_dir :: (v: Vec2, dir: Dir) -> Vec2 {
    if dir == {
        case .UP; return v;
        case .RIGHT; return .{ x = -v.y, y = v.x };
        case .DOWN; return .{ x = -v.x, y = -v.y };
        case .LEFT; return .{ x = v.y, y = -v.x };
    }

    assert(false, "Bad dir %", dir);
    return .{};
}

stich_cube :: (faces: [6]Cube_Face, map: [12]s64, map_width: s64) {
    map_height := 7 - map_width;

    for x: 0..map_width - 1 {
        // Map is 4 x 3 or 3 x 4. 6 because it would be 7 - map_width - 1.
        for y: 0..6 - map_width {
            if map[y * map_width + x] == -1  continue;

            face := *faces[map[y * map_width + x]];

            if y > 0 && map[(y - 1) * map_width + x] != -1 {
                face.neighbours[0].dir = .UP;
                face.neighbours[0].face = map[(y - 1) * map_width + x];
            }
            if x < map_width - 1 && map[y * map_width + x + 1] != -1 {
                face.neighbours[1].dir = .UP;
                face.neighbours[1].face = map[y * map_width + x + 1];
            }
            if y < map_height - 1 && map[(y + 1) * map_width + x] != -1 {
                face.neighbours[2].dir = .UP;
                face.neighbours[2].face = map[(y + 1) * map_width + x];
            }
            if x > 0 && map[y * map_width + x - 1] != -1 {
                face.neighbours[3].dir = .UP;
                face.neighbours[3].face = map[y * map_width + x - 1];
            }
        }
    }

    for 0..1 {
        for *faces {
            if it.neighbours[0].face != -1 {
                top_neighbour := *faces[it.neighbours[0].face];

                if it.neighbours[1].face != -1 {
                    right_neighbour := *faces[it.neighbours[1].face];

                    right_neighbour_top := rot_index(.UP, it.neighbours[1].dir);
                    right_neighbour.neighbours[right_neighbour_top].face = it.neighbours[0].face;
                    right_neighbour.neighbours[right_neighbour_top].dir  = rot_dir(it.neighbours[0].dir - it.neighbours[1].dir, false);

                    top_neighbour_right := rot_index(.RIGHT, it.neighbours[0].dir);
                    top\ _neighbour.neighbours[top_neighbour_right].face = it.neighbours[1].face;
                    top\ _neighbour.neighbours[top_neighbour_right].dir  = rot_dir(it.neighbours[1].dir - it.neighbours[0].dir, true);
                }
                if it.neighbours[3].face != -1 {
                    left_neighbour := *faces[it.neighbours[3].face];

                    left_neighbour_top := rot_index(.UP, it.neighbours[3].dir);
                    left_neighbour.neighbours[left_neighbour_top].face = it.neighbours[0].face;
                    left_neighbour.neighbours[left_neighbour_top].dir  = rot_dir(it.neighbours[0].dir - it.neighbours[3].dir, true);

                    top_neighbour_left := rot_index(.LEFT, it.neighbours[0].dir);
                    top\_neighbour.neighbours[top_neighbour_left].face = it.neighbours[3].face;
                    top\_neighbour.neighbours[top_neighbour_left].dir  = rot_dir(it.neighbours[3].dir - it.neighbours[0].dir, false);
                }
            }
            if it.neighbours[1].face != -1 {
                right_neighbour := *faces[it.neighbours[1].face];

                if it.neighbours[0].face != -1 {
                    top_neighbour := *faces[it.neighbours[0].face];

                    top_neighbour_right := rot_index(.RIGHT, it.neighbours[0].dir);
                    top\ _neighbour.neighbours[top_neighbour_right].face = it.neighbours[1].face;
                    top\ _neighbour.neighbours[top_neighbour_right].dir  = rot_dir(it.neighbours[1].dir - it.neighbours[0].dir, true);

                    right_neighbour_top := rot_index(.UP, it.neighbours[1].dir);
                    right_neighbour.neighbours[right_neighbour_top].face = it.neighbours[0].face;
                    right_neighbour.neighbours[right_neighbour_top].dir  = rot_dir(it.neighbours[0].dir - it.neighbours[1].dir, false);
                }
                if it.neighbours[2].face != -1 {
                    bottom_neighbour := *faces[it.neighbours[2].face];

                    bottom_neigbour_right := rot_index(.RIGHT, it.neighbours[2].dir);
                    bottom_neighbour.neighbours[bottom_neigbour_right].face = it.neighbours[1].face;
                    bottom_neighbour.neighbours[bottom_neigbour_right].dir  = rot_dir(it.neighbours[1].dir - it.neighbours[2].dir, false);

                    right_neighbour_bottom := rot_index(.DOWN, it.neighbours[1].dir);
                    right_neighbour.neighbours[right_neighbour_bottom].face = it.neighbours[2].face;
                    right_neighbour.neighbours[right_neighbour_bottom].dir  = rot_dir(it.neighbours[2].dir - it.neighbours[1].dir, true);
                }
            }
            if it.neighbours[2].face != -1 && false {
                bottom_neighbour := *faces[it.neighbours[2].face];

                if it.neighbours[1].face != -1 {
                    right_neighbour := *faces[it.neighbours[1].face];

                    right_neighbour_bottom := rot_index(.DOWN, it.neighbours[1].dir);
                    right_neighbour.neighbours[right_neighbour_bottom].face = it.neighbours[2].face;
                    right_neighbour.neighbours[right_neighbour_bottom].dir  = rot_dir(it.neighbours[2].dir - it.neighbours[1].dir, true);

                    bottom_neighbour_right := rot_index(.RIGHT, it.neighbours[2].dir);
                    bottom\ _neighbour.neighbours[bottom_neighbour_right].face = it.neighbours[1].face;
                    bottom\ _neighbour.neighbours[bottom_neighbour_right].dir  = rot_dir(it.neighbours[1].dir - it.neighbours[2].dir, false);
                }
                if it.neighbours[3].face != -1 {
                    left_neighbour := *faces[it.neighbours[3].face];

                    left_neighbour_bottom := rot_index(.DOWN, it.neighbours[3].dir);
                    left_neighbour.neighbours[left_neighbour_bottom].face = it.neighbours[2].face;
                    left_neighbour.neighbours[left_neighbour_bottom].dir  = rot_dir(it.neighbours[2].dir - it.neighbours[3].dir, false);

                    bottom_neighbour_left := rot_index(.LEFT, it.neighbours[2].dir);
                    bottom\_neighbour.neighbours[bottom_neighbour_left].face = it.neighbours[3].face;
                    bottom\_neighbour.neighbours[bottom_neighbour_left].dir  = rot_dir(it.neighbours[3].dir - it.neighbours[2].dir, true);
                }
            }
        }
    }

    for faces {
        print("%\n", it);
    }
}

rot_index :: (dir: Dir, neighbour_dir: Dir) -> s64 {
    return xx (dir + neighbour_dir) % 4;
}

rot_dir :: (dir: Dir, right: bool) -> Dir {
    res := xx ((xx dir + ifx right then 1 else -1) % 4);

    return xx ((ifx res > 0 then res else 4 + res) % 4);
}

convert_dir_to_val :: (dir: Vec2) -> u8 {
    assert(abs(dir.x) <= 1 && abs(dir.y) <= 1, "Bad dir values %", dir);
    if dir.x == 1  return 0;
    if dir.x == -1  return 2;
    if dir.y == 1  return 1;
    if dir.y == -1  return 3;
    return 0;
}

convert_dir_to_char :: (dir: Vec2) -> u8 {
    assert(abs(dir.x) <= 1 && abs(dir.y) <= 1, "Bad dir values %", dir);
    if dir.x == 1  return #char ">";
    if dir.x == -1  return #char "<";
    if dir.y == 1  return #char "v";
    if dir.y == -1  return #char "^";
    return 0;
}

turn :: (current: Vec2, turn: s64) -> Vec2 {
    return .{
        x = -current.y * turn,
        y = current.x * turn,
    };
}

parse_instructions :: (section: string) -> [..]Instruction {
    start := 0;

    instructions: [..]Instruction;

    while start < section.count {
        current := start;    

        while current < section.count && is_digit(section[current]) {
            current += 1;
        }

        array_add(*instructions, .{
            type = .WALK,
            value = string_to_int(slice(section, start, current - start)),
        });

        if current < section.count {
            array_add(*instructions, .{
                type = .TURN,
                value = ifx section[current] == #char "R" then 1 else -1,
            });
        }

        start = current + 1;
    }

    return instructions;
}

#scope_file
Instruction :: struct {
    value: s64;
    type: enum {
        TURN :: 0;
        WALK :: 1;
    } = .TURN;
}

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

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

Cube_Face :: struct {
    on_map: Vec2;
    neighbours: [4]struct {
        dir: Dir = .NONE;
        face: s64 = -1;
    };
}

Dir :: enum {
    NONE  :: 9;
    UP    :: 0;
    RIGHT :: 1;
    DOWN  :: 2;
    LEFT  :: 3;
};