const std = @import("std");

const PATH = "input/day14.txt";
const Str = []const u8;

pub fn first(allocator: std.mem.Allocator) !usize {
    _ = allocator;

    const RACE_TIME = 2503;

    var max: usize = 0;

    var lines = std.mem.tokenize(u8, @embedFile(PATH), "\n");
    while (lines.next()) |line| {
        var words = std.mem.tokenize(u8, line, " ");

        for (.{ "deer", "can", "fly" }) |_| {
            _ = words.next();
        }

        const speed = try std.fmt.parseUnsigned(usize, words.next().?, 10);

        for (.{ "km/s", "for" }) |_| {
            _ = words.next();
        }

        const duration = try std.fmt.parseUnsigned(usize, words.next().?, 10);

        for (.{ "seconds,", "but", "then", "must", "rest", "for" }) |_| {
            _ = words.next();
        }

        const rest = try std.fmt.parseUnsigned(usize, words.next().?, 10);

        const full_runs = RACE_TIME / (duration + rest);
        const left_time = RACE_TIME % (duration + rest);

        const run: usize = speed * duration * full_runs + std.math.min(duration, left_time) * speed;

        if (run > max) max = run;
    }

    return max;
}

pub fn second(allocator: std.mem.Allocator) !usize {
    const RACE_TIME = 2503;
    var deers = try parseInput(allocator, @embedFile(PATH));
    defer allocator.free(deers);

    var lead: usize = 0;

    var time: usize = 0;
    while (time < RACE_TIME) : (time += 1) {
        for (deers) |*deer| {
            const my_time = time % (deer.duration + deer.rest);
            if (my_time < deer.duration) deer.pos += deer.speed;

            if (deer.pos > lead) lead = deer.pos;
        }

        // give reward points
        for (deers) |*deer| {
            if (deer.pos == lead) deer.points += 1;
        }
    }

    var max: usize = 0;

    for (deers) |deer| {
        if (deer.points > max) max = deer.points;
    }

    return max;
}

const Deer = struct {
    speed: usize,
    duration: usize,
    rest: usize,
    pos: usize = 0,
    points: usize = 0,
};

fn parseInput(allocator: std.mem.Allocator, in: Str) ![]Deer {
    var deers = std.ArrayList(Deer).init(allocator);

    var lines = std.mem.tokenize(u8, in, "\n");
    while (lines.next()) |line| {
        var words = std.mem.tokenize(u8, line, " ");

        for (.{ "deer", "can", "fly" }) |_| {
            _ = words.next();
        }

        const speed = try std.fmt.parseUnsigned(usize, words.next().?, 10);

        for (.{ "km/s", "for" }) |_| {
            _ = words.next();
        }

        const duration = try std.fmt.parseUnsigned(usize, words.next().?, 10);

        for (.{ "seconds,", "but", "then", "must", "rest", "for" }) |_| {
            _ = words.next();
        }

        const rest = try std.fmt.parseUnsigned(usize, words.next().?, 10);

        try deers.append(.{ .speed = speed, .duration = duration, .rest = rest });
    }

    return try deers.toOwnedSlice();
}

test "day14a" {
    try std.testing.expectEqual(@as(usize, 2660), try first(std.testing.allocator));
}

test "day14b" {
    try std.testing.expectEqual(@as(usize, 1256), try second(std.testing.allocator));
}

const test_input =
    \\Comet can fly 14 km/s for 10 seconds, but then must rest for 127 seconds.
    \\Dancer can fly 16 km/s for 11 seconds, but then must rest for 162 seconds.
;