const std = @import("std");

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

const TEASPOON = 100;

pub fn first(allocator: std.mem.Allocator) !usize {
    const ings = try parseInput(allocator, @embedFile(PATH));
    defer allocator.free(ings);

    var max: usize = 0;

    var p: isize = 1;
    while (p < TEASPOON) : (p += 1) {
        var q: isize = 1;
        while (q < TEASPOON - p) : (q += 1) {
            var r: isize = 1;
            while (r < TEASPOON - p - q) : (r += 1) {
                var s: isize = 1;
                while (s < TEASPOON - p - q - r + 1) : (s += 1) {
                    if (p + q + r + s != 100) continue;

                    const capacity = std.math.max(0, ings[0].capacity * p +
                        ings[1].capacity * q +
                        ings[2].capacity * r +
                        ings[3].capacity * s);
                    if (capacity == 0) continue;
                    const durability = std.math.max(0, ings[0].durability * p +
                        ings[1].durability * q +
                        ings[2].durability * r +
                        ings[3].durability * s);
                    if (durability == 0) continue;
                    const flavor = std.math.max(0, ings[0].flavor * p +
                        ings[1].flavor * q +
                        ings[2].flavor * r +
                        ings[3].flavor * s);
                    if (flavor == 0) continue;
                    const texture = std.math.max(0, ings[0].texture * p +
                        ings[1].texture * q +
                        ings[2].texture * r +
                        ings[3].texture * s);
                    if (texture == 0) continue;

                    const val = capacity * durability * flavor * texture;
                    if (val > max) max = @intCast(usize, val);
                }
            }
        }
    }

    return max;
}

pub fn second(allocator: std.mem.Allocator) !usize {
    const TARGET_CALORIES = 500;

    const ings = try parseInput(allocator, @embedFile(PATH));
    defer allocator.free(ings);

    var max: usize = 0;

    var p: isize = 1;
    while (p < TEASPOON) : (p += 1) {
        var q: isize = 1;
        while (q < TEASPOON - p) : (q += 1) {
            var r: isize = 1;
            while (r < TEASPOON - p - q) : (r += 1) {
                var s: isize = 1;
                while (s < TEASPOON - p - q - r + 1) : (s += 1) {
                    if (p + q + r + s != 100) continue;

                    const calories =
                        ings[0].calories * p +
                        ings[1].calories * q +
                        ings[2].calories * r +
                        ings[3].calories * s;

                    if (calories != TARGET_CALORIES) continue;

                    const capacity = std.math.max(0, ings[0].capacity * p +
                        ings[1].capacity * q +
                        ings[2].capacity * r +
                        ings[3].capacity * s);
                    if (capacity == 0) continue;
                    const durability = std.math.max(0, ings[0].durability * p +
                        ings[1].durability * q +
                        ings[2].durability * r +
                        ings[3].durability * s);
                    if (durability == 0) continue;
                    const flavor = std.math.max(0, ings[0].flavor * p +
                        ings[1].flavor * q +
                        ings[2].flavor * r +
                        ings[3].flavor * s);
                    if (flavor == 0) continue;
                    const texture = std.math.max(0, ings[0].texture * p +
                        ings[1].texture * q +
                        ings[2].texture * r +
                        ings[3].texture * s);
                    if (texture == 0) continue;

                    const val = capacity * durability * flavor * texture;
                    if (val > max) max = @intCast(usize, val);
                }
            }
        }
    }

    return max;
}

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

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

const Ingredient = struct {
    capacity: isize,
    durability: isize,
    flavor: isize,
    texture: isize,
    calories: isize,
};

fn parseInput(allocator: std.mem.Allocator, input: Str) ![]Ingredient {
    var ingredients = std.ArrayList(Ingredient).init(allocator);
    defer ingredients.deinit();

    var lines = std.mem.tokenize(u8, input, "\n");
    while (lines.next()) |line| {
        var name_prop = std.mem.tokenize(u8, line, ":");
        _ = name_prop.next(); // drop name

        var props = std.mem.tokenize(u8, name_prop.next().?, ",");

        var ingr: Ingredient = undefined;
        var counter: usize = 0;
        while (props.next()) |prop| {
            var name_val = std.mem.tokenize(u8, prop, " ");
            _ = name_val.next();
            const val = try std.fmt.parseInt(isize, name_val.next().?, 10);
            switch (counter) {
                0 => ingr.capacity = val,
                1 => ingr.durability = val,
                2 => ingr.flavor = val,
                3 => ingr.texture = val,
                4 => ingr.calories = val,
                else => unreachable,
            }
            counter += 1;
        }

        try ingredients.append(ingr);
    }

    return try ingredients.toOwnedSlice();
}