const std = @import("std");

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

const RetType = u20;
const DigitType = u7;

const pattern_pieces = 10;
const output_pieces = 4;

const input_lines = 200;

const InputLine = struct {
    // patterns
    patterns: [pattern_pieces]DigitType = [_]DigitType{0} ** pattern_pieces,
    // output
    outputs: [output_pieces]DigitType = [_]DigitType{0} ** output_pieces,
    // 2, 3, 5
    five_len: [3]DigitType = [_]DigitType{0} ** 3,
    // 0, 6, 9
    six_len: [3]DigitType = [_]DigitType{0} ** 3,
};

const FullInput = [input_lines]InputLine;

pub fn parseInput() anyerror!FullInput {
    const input = @embedFile(path);
    var lines = std.mem.tokenize(u8, input, "\n");

    // This does not run the InputLine initialization
    var inl: FullInput = undefined;

    var i: usize = 0;
    while (lines.next()) |line| : (i += 1) {
        inl[i] = InputLine{};
        var pattern_output = std.mem.tokenize(u8, line, "|");
        var patts = std.mem.tokenize(u8, pattern_output.next().?, " ");
        var outs = std.mem.tokenize(u8, pattern_output.next().?, " ");

        // collect outs
        var idx: usize = 0;
        while (outs.next()) |out| : (idx += 1) {
            for (out) |ch| {
                inl[i].outputs[idx] |= @as(DigitType, 1) << @intCast(u3, ch - 'a');
            }
        }

        // collect patterns
        var five: usize = 0;
        var six: usize = 0;
        while (patts.next()) |patt| {
            var curr: *u7 = undefined;
            switch (patt.len) {
                2 => curr = &inl[i].patterns[1],
                4 => curr = &inl[i].patterns[4],
                3 => curr = &inl[i].patterns[7],
                7 => {
                    inl[i].patterns[8] = 0b1111111;
                    continue;
                },
                5 => {
                    curr = &inl[i].five_len[five];
                    five += 1;
                },
                6 => {
                    curr = &inl[i].six_len[six];
                    six += 1;
                },
                else => {
                    unreachable;
                },
            }
            for (patt) |ch| {
                curr.* |= @as(DigitType, 1) << @intCast(u3, ch - 'a');
            }
        }
    }

    return inl;
}

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

    var input = try parseInput();

    var sum: RetType = 0;
    for (input) |*line| {
        // 1. deduct 3 from 1
        for (line.five_len) |five| {
            if (five & line.patterns[1] == line.patterns[1]) {
                line.patterns[3] = five;
                break;
            }
        }

        // 2. deduct 9 from 4
        for (line.six_len) |six| {
            if (six & line.patterns[4] == line.patterns[4]) {
                line.patterns[9] = six;
                break;
            }
        }

        // 3. from six_len (discard 9!) the matching with 1 is 0, the other is 6
        for (line.six_len) |six| {
            if (six == line.patterns[9]) continue;
            if (six & line.patterns[1] == line.patterns[1]) {
                line.patterns[0] = six;
            } else {
                line.patterns[6] = six;
            }
        }

        // 4. from five_len (discard 3!) the matching with 9 is 5, the other is 2
        for (line.five_len) |five| {
            if (five == line.patterns[3]) continue;
            if (five & line.patterns[9] == five) {
                line.patterns[5] = five;
            } else {
                line.patterns[2] = five;
            }
        }

        // decode output
        var ret: RetType = 0;
        for (line.outputs) |out, idx| {
            for (line.patterns) |patt, i| {
                if (out == patt) {
                    ret += @intCast(RetType, i) *
                        try std.math.powi(RetType, 10, @intCast(RetType, 3 - idx));
                    break;
                }
            }
        }
        sum += ret;
    }

    return sum;
}

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, 1023686));

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

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