#import "Basic";
#import "File";
#import "String";

solve_day14 :: (test: bool) {
    contents := read_entire_file(ifx test then "inputs/day14_test.txt" else "inputs/day14.txt");
    lines := split(contents, "\n");

    grid_width := lines[0].count;

    part1 := 0;
    part2 := 0;

    history: [..]Grid;

    builder: String_Builder;
    for lines {
        assert(it.count == grid_width, "it.count != grid_width");
        print_to_builder(*builder, it);
    }

    grid_contents := builder_to_string(*builder);

    grid := Grid.{
        grid = grid_contents,
        width = grid_width,
        height = grid_contents.count / grid_width,
    };

    assert(grid.grid.count % grid.height == 0, "grid.grid.count % grid.height != 0");

    total_cycles := 1_000_000_000;
    cycle_length := 0;
    cycle_start  := 0;

    tilt(.NORTH, grid);

    part1 = xx calc(grid);
    tilt(.WEST, grid);
    tilt(.SOUTH, grid);
    tilt(.EAST, grid);

    array_add(*history, copy(grid));

    for outer: 1..total_cycles {
        tilt(.NORTH, grid);
        tilt(.WEST, grid);
        tilt(.SOUTH, grid);
        tilt(.EAST, grid);
        array_add(*history, copy(grid));

        for history {
            if it_index + 1 == history.count {
                break;
            }
            if it == grid {
                cycle_length = outer - it_index;
                cycle_start = it_index + 1;

                break outer;
            }
        }
    }

    assert(cycle_start != 0 && cycle_length != 0, "Cycle start: %, cycle length: %", cycle_start, cycle_length);

    last_grid_index := cycle_start + (total_cycles - cycle_start) % cycle_length;

    last_grid := history[last_grid_index - 1];

    part2 = xx calc(last_grid);

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

operator == :: (g1: Grid, g2: Grid) -> bool {
    assert(g1.height == g2.height);
    assert(g1.width == g2.width);
    for 0..g1.height - 1 {
        if slice(g1.grid, it * g1.width, g1.width) != slice(g2.grid, it * g2.width, g2.width) {
            return false;
        }
    }
    return true;
}

copy :: (grid: Grid) -> Grid {
    return .{
        width = grid.width,
        height = grid.height,
        grid = copy_string(grid.grid),
    };
}

print_grid :: (grid: Grid) {
    print("--------\n");
    for 0..grid.height - 1 {
        print("%\n", slice(grid.grid, grid.width * it, grid.width));
    }
}

calc :: (grid: Grid) -> u64 {
    result: u64 = 0;

    for row: 0..grid.height - 1 {
        for column: 0..grid.width - 1 {
            if grid.grid[row * grid.width + column] == #char "O" {
                result += xx (grid.height - row);
            }
        }
    }

    return result;
}

tilt :: (dir: Tilt_Direction, grid: Grid) {
    if dir == {
        case .NORTH;
            tilt_north(grid);
        case .SOUTH;
            tilt_south(grid);
        case .WEST;
            tilt_west(grid);
        case .EAST;
            tilt_east(grid);
    }
}

tilt_south :: (grid: Grid) {
    for r: 1..grid.height - 1 {
        row := grid.height - 1 - r;

        for column: 0..grid.width - 1 {
            if grid.grid[row * grid.width + column] == #char "O" {
                t := row;

                while t < grid.height - 1 {
                    defer t += 1;

                    if grid.grid[(t + 1) * grid.width + column] == #char "." {
                        grid.grid[(t + 1) * grid.width + column] = #char "O";
                        grid.grid[t * grid.width + column] = #char ".";
                    } else {
                        break;
                    }
                }
            }
        }
    }
}

tilt_east :: (grid: Grid) {
    for c: 1..grid.width - 1 {
        column := grid.width - 1 - c;

        for row: 0..grid.height - 1 {
            if grid.grid[row * grid.width + column] == #char "O" {
                t := column;

                while t < grid.width - 1 {
                    defer t += 1;

                    if grid.grid[row * grid.width + t + 1] == #char "." {
                        grid.grid[row * grid.width + t + 1] = #char "O";
                        grid.grid[row * grid.width + t] = #char ".";
                    } else {
                        break;
                    }
                }
            }
        }
    }
}

tilt_north :: (grid: Grid) {
    for row: 1..grid.height - 1 {
        for column: 0..grid.width - 1 {
            if grid.grid[row * grid.width + column] == #char "O" {
                t := row;

                while t > 0 {
                    defer t -= 1;

                    if grid.grid[(t - 1) * grid.width + column] == #char "." {
                        grid.grid[(t - 1) * grid.width + column] = #char "O";
                        grid.grid[t * grid.width + column] = #char ".";
                    } else {
                        break;
                    }
                }
            }
        }
    }
}

tilt_west :: (grid: Grid) {
    for column: 1..grid.width - 1 {
        for row: 0..grid.height - 1 {
            if grid.grid[row * grid.width + column] == #char "O" {
                t := column;

                while t > 0 {
                    defer t -= 1;

                    if grid.grid[row * grid.width + t - 1] == #char "." {
                        grid.grid[row * grid.width + t - 1] = #char "O";
                        grid.grid[row * grid.width + t] = #char ".";
                    } else {
                        break;
                    }
                }
            }
        }
    }
}


Tilt_Direction :: enum {
    NORTH;
    EAST;
    SOUTH;
    WEST;
}