const std = @import("std");

pub const Policy = struct {
    min: u32 = 0,
    max: u32 = 0,
    value: u8,
    password: []u8,

    pub fn matches_policy_one(self: *Policy, s: []u8) bool {
        var c : u32 = 0;
        for(s) | v | {
            if (@as(u8, v) == self.value) {
                c += 1;
            }
        }
        var valid = (c >= self.min and c <= self.max);
        //std.log.debug("{} is {} for Policy {}-{} of {c} [{} found]",
        //              .{s, valid, self.min, self.max, self.value, c});
        return valid;
    }

    pub fn matches_policy_two(self: *Policy, s: []u8) bool {
        var n_matches : u32 = 0;
        if (s[self.min-1] == self.value) {
            n_matches += 1;
        }
        if (s[self.max-1] == self.value) {
            n_matches += 1;
        }
        return n_matches == 1;
    }
};

pub fn main() !void {
    var allocator = std.heap.GeneralPurposeAllocator(.{}){};
    var f = std.fs.File { .handle = try std.os.open("input", std.os.O_RDONLY, 0) };
    var buffer : [1024]u8 = undefined;

    var byte_buffer = std.ArrayList(u8).init(&allocator.allocator);
    defer byte_buffer.deinit();

    const stdout = std.io.getStdOut().writer();
    var n_valid : u32 = 0;
    var read = try f.read(&buffer);
    std.log.info("Read {} bytes", .{read});
    while (read != 0) {
        for (buffer) | v, k | {
            // We're through the read part of the buffer
            if (k >= read) {
                break;
            }
            if (v == '\n') {
                var p = parse_policy(byte_buffer.items[0..]);
                if (p.matches_policy_two(p.password)) {
                    n_valid += 1;
                }
                try stdout.print("{}-{} {c}: {}\n", .{p.min, p.max, p.value, p.password});
                //std.log.debug("{}", .{x});
                // Set the position back to zero, without freeing existing memory
                byte_buffer.deinit();
                byte_buffer = std.ArrayList(u8).init(&allocator.allocator);
                continue;
            }
            try byte_buffer.append(v);
        }
        read = try f.read(&buffer);
        std.log.info("Read {} bytes", .{read});
    }
    std.log.info("{} valid items", .{n_valid});
    //part1(expenses.items[0..]);
    //part2(expenses.items[0..]);
}

fn parse_policy(line: []u8) Policy {
    var min : u32 = 0;
    var max : u32 = 0;
    var char : u8 = 0;
    var pass : []u8 = undefined;

    var start : usize = 0;
    var got_min = false;
    var got_max = false;
    for (line) | v, k | {
        if (v == '-') {
            min = atoi(line[start..k]);
            start = k + 1;
            got_min = true;
        }
        if (v == ' ' and !got_max) {
            max = atoi(line[start..k]);
            start = k + 1;
            got_max = true;
        }
        if (v == ':') {
            char = line[start..k][0];
            start = k + 2;
        }
        if (k == line.len-1) {
            pass = line[start..];
        }
    }
    return Policy {
        .min = min,
        .max = max,
        .value = char,
        .password = pass,
    };
}

fn part2() void {
}

fn part1() void {
}

fn atoi(a: []u8) u32 {
    var i : u32 = 0;
    for(a) |v, k| {
        if (! std.ascii.isDigit(v)) {
            std.log.warn("Byte {x} is not a digit", .{v});
            continue;
        }
        // 48 is '0' in ascii
        std.debug.assert(v >= 48 and v < 58);
        i += @as(u32, (v - 48) * std.math.pow(u32, 10, @intCast(u32, a.len - k - 1)));
    }
    //std.log.debug("{x} --> {}", .{a, i});
    return i;
}