const std = @import("std");

const ArrayList = std.ArrayList;

const Vec2 = struct {
    x: i32,
    y: i32,
};

pub fn solve_part1(allocator: std.mem.Allocator, file: []const u8) !void {
    var grid = ArrayList([]u8).init(allocator);
    var walked = ArrayList([]u8).init(allocator);

    var inputs = try std.fs.cwd().openFile(file, .{});

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

    var buffer: [1024]u8 = undefined;

    while (try in_stream.readUntilDelimiterOrEof(&buffer, '\n')) |line| {
        var line_copy = try allocator.dupe(u8, line);

        var empty = try allocator.alloc(u8, line_copy.len);
        @memset(empty, 0);
        try grid.append(line_copy);
        try walked.append(empty);
    }

    walk_beam(grid, &walked, .{ .x = 1, .y = 0 }, .{ .x = 0, .y = 0 });

    var part1_result: u64 = 0;

    for (walked.items) |l| {
        for (l) |c| {
            if (c > 0) part1_result += 1;
        }
        @memset(l, 0);
    }
    std.log.info("Part 1 result: {d}", .{part1_result});

    var part2_result: u64 = 0;

    for (0..grid.items[0].len) |start_pos_x| {
        walk_beam(grid, &walked, .{ .x = 0, .y = 1 }, .{ .x = @intCast(start_pos_x), .y = 0 });
        var h: u64 = 0;

        for (walked.items) |l| {
            for (l) |c| {
                if (c > 0) h += 1;
            }
            @memset(l, 0);
        }

        part2_result = @max(part2_result, h);
    }
    for (0..grid.items[0].len) |start_pos_x| {
        walk_beam(grid, &walked, .{ .x = 0, .y = -1 }, .{ .x = @intCast(start_pos_x), .y = @intCast(grid.items.len - 1) });
        var h: u64 = 0;

        for (walked.items) |l| {
            for (l) |c| {
                if (c > 0) h += 1;
            }
            @memset(l, 0);
        }

        part2_result = @max(part2_result, h);
    }
    for (0..grid.items.len) |start_pos_y| {
        walk_beam(grid, &walked, .{ .x = -1, .y = 0 }, .{ .y = @intCast(start_pos_y), .x = @intCast(grid.items[0].len - 1) });
        var h: u64 = 0;

        for (walked.items) |l| {
            for (l) |c| {
                if (c > 0) h += 1;
            }
            @memset(l, 0);
        }

        part2_result = @max(part2_result, h);
    }
    for (0..grid.items.len) |start_pos_y| {
        walk_beam(grid, &walked, .{ .x = 1, .y = 0 }, .{ .y = @intCast(start_pos_y), .x = 0 });
        var h: u64 = 0;

        for (walked.items) |l| {
            for (l) |c| {
                if (c > 0) h += 1;
            }
            @memset(l, 0);
        }

        part2_result = @max(part2_result, h);
    }
    std.log.info("Part 2 result: {d}", .{part2_result});
}

fn walk_beam(grid: ArrayList([]u8), walked: *ArrayList([]u8), direction: Vec2, position: Vec2) void {
    var walk_pos = position;
    var walk_dir = direction;

    while (walk_pos.x < walked.items[0].len and walk_pos.x >= 0 and walk_pos.y < walked.items.len and walk_pos.y >= 0) {
        var wx = @as(usize, @intCast(walk_pos.x));
        var wy = @as(usize, @intCast(walk_pos.y));

        var flag = dir_to_bit_flag(walk_dir);

        if (walked.items[wy][wx] & flag == flag) {
            break;
        }

        walked.items[wy][wx] |= dir_to_bit_flag(walk_dir);

        var cur = grid.items[wy][wx];

        if (cur == '/') {
            walk_dir = forward_mirror(walk_dir);
        } else if (cur == '\\') {
            walk_dir = backward_mirror(walk_dir);
        }

        if (cur == '-') {
            var p = Vec2{ .x = walk_pos.x - 1, .y = walk_pos.y };
            walk_beam(grid, walked, .{ .x = -1, .y = 0 }, p);
            p.x += 2;
            walk_beam(grid, walked, .{ .x = 1, .y = 0 }, p);
            break;
        } else if (cur == '|') {
            var p = Vec2{ .x = walk_pos.x, .y = walk_pos.y - 1 };
            walk_beam(grid, walked, .{ .x = 0, .y = -1 }, p);
            p.y += 2;
            walk_beam(grid, walked, .{ .x = 0, .y = 1 }, p);
            break;
        }
        walk_pos.x += @intCast(walk_dir.x);
        walk_pos.y += @intCast(walk_dir.y);
    }
}

fn forward_mirror(dir: Vec2) Vec2 {
    if (dir.x == 0 and dir.y == 1) {
        return .{ .x = -1, .y = 0 };
    } else if (dir.x == 0 and dir.y == -1) {
        return .{ .x = 1, .y = 0 };
    } else if (dir.x == 1 and dir.y == 0) {
        return .{ .x = 0, .y = -1 };
    } else if (dir.x == -1 and dir.y == 0) {
        return .{ .x = 0, .y = 1 };
    }

    std.log.err("Bad dir: x {d}  y {d}", .{ dir.x, dir.y });
    std.os.exit(1);
    return .{};
}

fn backward_mirror(dir: Vec2) Vec2 {
    if (dir.x == 0 and dir.y == 1) {
        return .{ .x = 1, .y = 0 };
    } else if (dir.x == 0 and dir.y == -1) {
        return .{ .x = -1, .y = 0 };
    } else if (dir.x == 1 and dir.y == 0) {
        return .{ .x = 0, .y = 1 };
    } else if (dir.x == -1 and dir.y == 0) {
        return .{ .x = 0, .y = -1 };
    }

    std.log.err("Bad dir: x {d}  y {d}", .{ dir.x, dir.y });
    std.os.exit(1);
    return .{};
}

fn dir_to_bit_flag(dir: Vec2) u8 {
    if (dir.x == 1 and dir.y == 0) {
        return 2;
    } else if (dir.x == -1 and dir.y == 0) {
        return 4;
    } else if (dir.x == 0 and dir.y == 1) {
        return 8;
    } else if (dir.x == 0 and dir.y == -1) {
        return 16;
    }
    return 0;
}