#import "Basic";
#import "String";
#import "File";
#import "Math";
#import "Hash_Table";

solve_day19 :: (test: bool) {
    contents := read_entire_file(ifx test then "inputs/day19_test.txt" else "inputs/day19.txt");
    sections := split(contents, "\n\n");

    debug_it := true;

    part1 := 0;
    part2 := 0;

    workflows: Table(string, [..]Rule);
    workflow_order: [..]string;

    workflow_lines := split(sections[0], "\n"[0]);

    for line: workflow_lines {
        a := split(line, "{");
        workflow_name := a[0];
        rules := split(slice(a[1], 0, a[1].count - 1), ",");

        workflow_rules: [..]Rule;

        for rule: rules {
            array_add(*workflow_rules, parse_rule(rule));
        }

        table_add(*workflows, workflow_name, workflow_rules);
        array_add(*workflow_order, workflow_name);
    }

    run_section_lines := split(sections[1], "\n"[0]);

    for line: run_section_lines {
        part := parse_part(slice(line, 1, line.count - 2));

        is_rejected := false;
        is_accepted := false;
        
        current, found := table_find(*workflows, "in");

        while !is_rejected && !is_accepted {
            for item: current {
                a: u32 = 0;

                if item.part == {
                    case #char "d";
                    if item.workflow[0] == #char "A" {
                        is_accepted = true;
                        break;
                    } else if item.workflow[0] == #char "R" {
                        is_rejected = true;
                        break;
                    } else {
                        current, found = table_find(*workflows, item.workflow);
                        break;
                    }
                    case #char "x"; 
                        a = part.x;
                    case #char "m";
                        a = part.m;
                    case #char "a";
                        a = part.a;
                    case #char "s";
                        a = part.s;
                    case;
                        assert(false, "Bad part: %", item.part);
                }

                if item.comparator == #char ">" {
                    if a > item.amount {
                        if item.workflow[0] == #char "A" {
                            is_accepted = true;
                            break;
                        } else if item.workflow[0] == #char "R" {
                            is_rejected = true;
                            break;
                        }
                        current, found = table_find(*workflows, item.workflow);
                        break;
                    }
                } else if item.comparator == #char "<" {
                    if a < item.amount {
                        if item.workflow[0] == #char "A" {
                            is_accepted = true;
                            break;
                        } else if item.workflow[0] == #char "R" {
                            is_rejected = true;
                            break;
                        }
                        current, found = table_find(*workflows, item.workflow);
                        break;
                    }
                }
            }
        }

        if is_accepted {
            part1 += part.x + part.m + part.a + part.s;
        }
    }

    print("Part 1: %\n", part1);

    stack: [..]Queue_Part;

    array_add(*stack, .{
                          part = .{
                              x = Range.{ start = 1, stop = 4000 },
                              m = Range.{ start = 1, stop = 4000 },
                              a = Range.{ start = 1, stop = 4000 },
                              s = Range.{ start = 1, stop = 4000 },
                         },
                         workflow = "in"
                     });

    while stack.count > 0 {
        part := stack[stack.count - 1];
        array_unordered_remove_by_index(*stack, stack.count - 1);

        if part.workflow[0] == #char "A" {
            part2 += xx length(part.part);
            continue;
        } else if part.workflow[0] == #char "R" {
            continue;
        }

        workflow, found := table_find(*workflows, part.workflow);

        for rule, rule_i: array_view(workflow, 0, workflow.count - 1) {
            part_b := part;
            part_b.workflow = rule.workflow;

            compare_amount: Range = ---;
            xmas_a: Range = ---;
            xmas_b: Range = ---;

            if rule.part == {
                case #char "x";
                    compare_amount = part.part.x;
                case #char "m";
                    compare_amount = part.part.m;
                case #char "a";
                    compare_amount = part.part.a;
                case #char "s";
                    compare_amount = part.part.s;
                case;
                    assert(false, "Bad part in rule: %", rule.part);
            }

            if rule.comparator == #char ">" {
                if compare_amount.stop <= rule.amount  continue;

                if compare_amount.start > rule.amount {
                    part.workflow = rule.workflow;
                    array_add(*stack, part);
                    break;
                }

                xmas_a = .{ start = compare_amount.start, stop = rule.amount };
                xmas_b = .{ start = rule.amount + 1, stop = compare_amount.stop };
            } else if rule.comparator == #char "<" {
                if compare_amount.start >= rule.amount  continue;

                if compare_amount.stop < rule.amount {
                    part.workflow = rule.workflow;
                    array_add(*stack, part);
                    break;
                }

                xmas_a = .{ start = rule.amount, stop = compare_amount.stop };
                xmas_b = .{ start = compare_amount.start, stop = rule.amount - 1 };
            } else {
                assert(false, "Invalid comparator: %", rule.comparator);
            }

            if rule.part == {
                case #char "x";
                    part.part.x = xmas_a;
                    part_b.part.x = xmas_b;
                case #char "m";
                    part.part.m = xmas_a;
                    part_b.part.m = xmas_b;
                case #char "a";
                    part.part.a = xmas_a;
                    part_b.part.a = xmas_b;
                case #char "s";
                    part.part.s = xmas_a;
                    part_b.part.s = xmas_b;
                case;
                    assert(false);
            }

            array_add(*stack, part_b);
        }

        part.workflow = workflow[workflow.count - 1].workflow;

        array_add(*stack, part);
    }

    print("Part 2: %\n", part2);
}

parse_part :: (p: string) -> (part: Part, success: bool) {
    ps := split(p, ",");

    x, success_x := string_to_int(slice(ps[0], 2, ps[0].count - 2), 10, u32);
    m, success_m := string_to_int(slice(ps[1], 2, ps[1].count - 2), 10, u32);
    a, success_a := string_to_int(slice(ps[2], 2, ps[2].count - 2), 10, u32);
    s, success_s := string_to_int(slice(ps[3], 2, ps[3].count - 2), 10, u32);

    return .{
        x = x,
        m = m,
        a = a,
        s = s,
    }, success_x && success_m && success_a && success_s;
}

parse_rule :: (p: string) -> (rule: Rule, success: bool) {
    number_split := split(p, ":");

    part := number_split[0];

    rule := Rule.{
        part = part[0],
        comparator = 0,
        amount = 0,
        workflow = "",
    };

    if number_split.count > 1 {
        workflow := number_split[1];

        rule.comparator = part[1];
        amount := string_to_int(slice(part, 2, part.count - 2), 10, u32);

        rule.amount = amount;
        rule.workflow = workflow;
    } else {
        rule.part = #char "d";
        rule.workflow = part;
    }

    return rule, true;

}

length_inclusive :: (range: Range) -> u64 {
    if range.stop < range.start {
        return 0;
    } else {
        return range.stop - range.start + 1;
    }
}

length :: (pr: Part_Range) -> u64 {
    return length_inclusive(pr.x) * length_inclusive(pr.m) * length_inclusive(pr.a) * length_inclusive(pr.s);
}

Rule :: struct {
    part: u8;
    comparator: u8;
    amount: u32;
    workflow: string;
}

Queue_Part :: struct {
    part: Part_Range;
    workflow: string;
}

Range :: struct {
    start: u32;
    stop: u32;
}

Part_Range :: struct {
    x: Range;
    m: Range;
    a: Range;
    s: Range;
}

Part :: struct {
    x: u32;
    m: u32;
    a: u32;
    s: u32;
}