const std = @import("std");

const ArrayList = std.ArrayList;

pub fn solve_part1(allocator: std.mem.Allocator, file: []const u8) !void {
    var inputs = try std.fs.cwd().openFile(file, .{});

    var buffered_reader = std.io.bufferedReader(inputs.reader());

    var in_stream = buffered_reader.reader();

    var grid: [][100]u8 = try allocator.alloc([100]u8, 100);
    @memset(grid, [_]u8{0} ** 100);

    var buffer: [200]u8 = undefined;

    var history = ArrayList([][100]u8).init(allocator);

    var rows: u32 = 0;
    var columns: u32 = 0;

    while (try in_stream.readUntilDelimiterOrEof(&buffer, '\n')) |line| {
        columns = @intCast(line.len);
        @memcpy(grid[rows][0..columns], line);
        rows += 1;
    }

    var total_cycles: u32 = 1000000000;

    var cycle_length: u32 = 0;
    var cycle_start: u32 = 0;

    var d: u32 = 0;
    _ = d;

    tilt(.NORTH, grid, rows, columns);
    var part1_result = calc(grid, rows);
    tilt(.WEST, grid, rows, columns);
    tilt(.SOUTH, grid, rows, columns);
    tilt(.EAST, grid, rows, columns);
    var grid_new: [][100]u8 = try allocator.alloc([100]u8, 100);
    @memcpy(grid_new, grid);
    try history.append(grid_new);

    outer: for (1..total_cycles) |i| {
        tilt(.NORTH, grid, rows, columns);
        tilt(.WEST, grid, rows, columns);
        tilt(.SOUTH, grid, rows, columns);
        tilt(.EAST, grid, rows, columns);
        grid_new = try allocator.alloc([100]u8, 100);
        @memcpy(grid_new, grid);

        try history.append(grid_new);

        std.log.info("{d}", .{calc(history.items[i], rows)});

        for (history.items, 0..) |grid_other, grid_i| {
            if (grid_i + 1 == history.items.len) {
                break;
            }
            if (equals(grid_other, grid, rows, columns)) {
                cycle_length = @intCast(i - (grid_i));
                cycle_start = @intCast(grid_i + 1);

                break :outer;
            }
        }
    }

    std.log.info("Start: {d}", .{cycle_start});
    std.log.info("Length: {d}", .{cycle_length});

    if (total_cycles == 0 or cycle_start == 0 or cycle_length == 0) {
        std.log.info("Failed to find cycle", .{});
        std.os.exit(1);
    }

    var last_grid_i: usize = cycle_start + (total_cycles - cycle_start) % cycle_length;

    var last_grid = history.items[last_grid_i - 1];

    std.log.info("Part 1: {d}", .{part1_result});
    var part2_result: u64 = calc(last_grid, rows);
    std.log.info("Part 2: {d}", .{part2_result});
}

fn equals(grid_1: [][100]u8, grid_2: [][100]u8, rows: u32, columns: u32) bool {
    for (0..rows) |r| {
        if (!std.mem.eql(u8, grid_1[r][0..columns], grid_2[r][0..columns])) {
            return false;
        }
    }

    return true;
}

fn calc(grid: [][100]u8, rows: u32) u64 {
    var result: u64 = 0;

    for (0..grid.len) |i| {
        for (0..grid[i].len) |j| {
            if (grid[i][j] == 'O') {
                result += rows - i;
            }
        }
    }

    return result;
}

const TilDirection = enum {
    NORTH,
    EAST,
    SOUTH,
    WEST,
};

const Direction = struct {
    x: i32 = 0,
    y: i32 = 0,
};

fn tilt(tilt_direction: TilDirection, grid: [][100]u8, rows: u32, columns: u32) void {
    switch (tilt_direction) {
        .NORTH => tilt_north(grid, rows, columns),
        .SOUTH => tilt_south(grid, rows, columns),
        .WEST => tilt_west(grid, rows, columns),
        .EAST => tilt_east(grid, rows, columns),
    }
}

fn tilt_south(grid: [][100]u8, rows: u32, columns: u32) void {
    for (1..rows) |r| {
        var row = rows - r - 1;
        for (0..columns) |column| {
            if (grid[row][column] == 'O') {
                var t = row;

                while (t < rows - 1) : (t += 1) {
                    if (grid[t + 1][column] == '.') {
                        grid[t + 1][column] = 'O';
                        grid[t][column] = '.';
                    } else {
                        break;
                    }
                }
            }
        }
    }
}

fn tilt_east(grid: [][100]u8, rows: u32, columns: u32) void {
    for (1..columns) |c| {
        var column = columns - c - 1;
        for (0..rows) |row| {
            if (grid[row][column] == 'O') {
                var t = column;

                while (t < rows - 1) : (t += 1) {
                    if (grid[row][t + 1] == '.') {
                        grid[row][t + 1] = 'O';
                        grid[row][t] = '.';
                    } else {
                        break;
                    }
                }
            }
        }
    }
}

fn tilt_north(grid: [][100]u8, rows: u32, columns: u32) void {
    for (1..rows) |row| {
        for (0..columns) |column| {
            if (grid[row][column] == 'O') {
                var t = row;

                while (t > 0) : (t -= 1) {
                    if (grid[t - 1][column] == '.') {
                        grid[t - 1][column] = 'O';
                        grid[t][column] = '.';
                    } else {
                        break;
                    }
                }
            }
        }
    }
}

fn tilt_west(grid: [][100]u8, rows: u32, columns: u32) void {
    for (1..columns) |column| {
        for (0..rows) |row| {
            if (grid[row][column] == 'O') {
                var t = column;

                while (t > 0) : (t -= 1) {
                    if (grid[row][t - 1] == '.') {
                        grid[row][t - 1] = 'O';
                        grid[row][t] = '.';
                    } else {
                        break;
                    }
                }
            }
        }
    }
}