const std = @import("std");

const path = "data/day05/input.txt";

const RetType = u16;

const grid_size = 1000;
const max_grid_value = u2;
const Vents = [grid_size][grid_size]max_grid_value;

const CoordType = i20;
const Coord = struct { x: CoordType, y: CoordType };

fn countVents(directions: []const Coord, nodiag: bool) anyerror!RetType {
    const input = @embedFile(path);
    var lines = std.mem.split(u8, input, "\n");

    var vts: Vents = [_][grid_size]max_grid_value{[_]max_grid_value{0} ** grid_size} ** grid_size;

    var counter: RetType = 0;
    while (lines.next()) |line| {
        if (line.len == 0) continue;
        var crop_line = std.mem.tokenize(u8, line, " -> ");

        // parse coordinates
        var l: [2]Coord = undefined;
        var idx: usize = 0;
        while (crop_line.next()) |cl| : (idx += 1) {
            var coords = std.mem.tokenize(u8, cl, ",");
            l[idx].x = try std.fmt.parseUnsigned(CoordType, coords.next().?, 10);
            l[idx].y = try std.fmt.parseUnsigned(CoordType, coords.next().?, 10);
        }

        // check if diagonal lines count or not
        if (nodiag and !((l[0].x == l[1].x) or (l[0].y == l[1].y))) continue;

        // setup lines
        const dir = try getDirection(l, directions);
        while ((l[0].x != l[1].x + dir.x) or (l[0].y != l[1].y + dir.y)) : ({
            l[0].x += dir.x;
            l[0].y += dir.y;
        }) {
            var v = vts[@intCast(usize, l[0].x)][@intCast(usize, l[0].y)];
            if (v == 1) {
                counter += 1;
            }
            v +|= 1;
            vts[@intCast(usize, l[0].x)][@intCast(usize, l[0].y)] = v;
        }
    }

    return counter;
}

fn getDirection(c: [2]Coord, directions: []const Coord) !Coord {
    var min = try manhattan(c[0], c[1]);
    for (directions) |d| {
        if ((try manhattan(Coord{ .x = c[0].x + d.x, .y = c[0].y + d.y }, c[1])) < min) {
            return d;
        }
    }
    unreachable;
}

fn manhattan(f: Coord, s: Coord) !CoordType {
    const xdiff = try std.math.absInt(f.x - s.x);
    const ydiff = try std.math.absInt(f.y - s.y);
    return xdiff + ydiff;
}

pub fn second(allocator: ?std.mem.Allocator) anyerror!RetType {
    _ = allocator;

    const directions = [_]Coord{
        // Diagonal manhattan distance can decrease by 2, so these must be
        // tried first otherwise getDirection() will not return proper direction.
        .{ .x = -1, .y = -1 },
        .{ .x = -1, .y = 1 },
        .{ .x = 1, .y = -1 },
        .{ .x = 1, .y = 1 },

        .{ .x = -1, .y = 0 },
        .{ .x = 1, .y = 0 },
        .{ .x = 0, .y = -1 },
        .{ .x = 0, .y = 1 },
    };
    return try countVents(directions[0..], false);
}

pub fn main() anyerror!void {
    var timer = try std.time.Timer.start();
    const ret = try second(null);
    const s = timer.lap() / 1000;

    try std.testing.expectEqual(ret, @as(RetType, 21577));

    std.debug.print("Day 5b result: {d} \t\ttime: {d}us\n", .{ ret, s });
}

test "day05b" {
    try std.testing.expectEqual(@as(RetType, 21577), try second(std.testing.allocator));
}