const std = @import("std");

const Str = []const u8;
const INPUT = "cqjxjnds";

pub fn first(allocator: std.mem.Allocator) !Str {
    var input: [INPUT.len]u8 = undefined;
    for (INPUT) |ch, idx| {
        input[idx] = ch;
    }

    return std.fmt.allocPrint(allocator, "{s}", .{nextPw(&input)});
}

pub fn second(allocator: std.mem.Allocator) !Str {
    var input: [INPUT.len]u8 = undefined;
    for (INPUT) |ch, idx| {
        input[idx] = ch;
    }
    _ = nextPw(&input);

    // move the pw forward
    movePw(&input);

    return std.fmt.allocPrint(allocator, "{s}", .{nextPw(&input)});
}

test "day11a" {
    const res = try first(std.testing.allocator);
    defer std.testing.allocator.free(res);
    try std.testing.expectEqualStrings("cqjxxyzz", res);
}

test "day11b" {
    const res = try second(std.testing.allocator);
    defer std.testing.allocator.free(res);
    try std.testing.expectEqualStrings("cqkaabcc", res);
}

fn nextPw(in: []u8) Str {
    // change the first disallowed char in advance
    for (in) |*ch, idx| {
        switch (ch.*) {
            'i', 'o', 'l' => {
                ch.* = ch.* + 1;
                idx += 1;
                while (idx < in.len) : (idx += 1) {
                    in[idx] = 'a';
                }
                break;
            },
            else => {},
        }
    }

    while (true) {
        if (checkPw(in)) return in;
        movePw(in);
    }

    unreachable;
}

test "nextPw" {
    var input: [8]u8 = .{ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' };
    try std.testing.expectEqualStrings("abcdffaa", nextPw(&input));

    input = .{ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n' };
    try std.testing.expectEqualStrings("ghjaabcc", nextPw(&input));
}

fn movePw(in: []u8) void {
    var idx: usize = 8;
    while (idx > 0) : (idx -= 1) {
        if (in[idx - 1] == 'z') {
            in[idx - 1] = 'a';
        } else {
            in[idx - 1] = in[idx - 1] + 1;
            break;
        }
    }
}

fn checkPw(in: Str) bool {
    if (in.len < 3) return false;

    var straight: bool = false;
    var st_start: u8 = in[0];

    var doubles: usize = 0;
    var db_prev: u8 = in[0];
    var overlap: bool = false;

    var idx: usize = 1;
    while (idx < in.len) : (idx += 1) {
        switch (in[idx]) {
            'i', 'o', 'l' => return false,
            else => {
                // check straight
                if (!straight and idx != in.len - 1) {
                    if (in[idx] - 1 == st_start and in[idx + 1] - 2 == st_start) {
                        straight = true;
                    } else {
                        st_start = in[idx];
                    }
                }

                // check doubles
                if (doubles < 2 and !overlap) {
                    // std.debug.print("{}: {c} {c}\n", .{ idx, db_prev, in[idx] });
                    if (in[idx] == db_prev) {
                        doubles += 1;
                        overlap = true;
                    }
                } else {
                    overlap = false;
                }
                db_prev = in[idx];
            },
        }
    }

    // std.debug.print("{s}: {} {}\n", .{ in, straight, doubles });
    return straight and (doubles > 1);
}

test "checkPw" {
    try std.testing.expectEqual(false, checkPw("hijklmmn"));
    try std.testing.expectEqual(false, checkPw("abbceffg"));
    try std.testing.expectEqual(false, checkPw("abbcegjk"));
    try std.testing.expectEqual(false, checkPw("abcdefgh"));
    try std.testing.expectEqual(true, checkPw("abcdffaa"));
    try std.testing.expectEqual(false, checkPw("ghijklmn"));
    try std.testing.expectEqual(true, checkPw("ghjaabcc"));
}