from collections import defaultdict, Counter, deque
from itertools import product
from multiprocessing import Pool
import math
import re

non_digits = re.compile('[^0-9]+')
def sign(a, b, step=1):
    return int(math.copysign(step, b-a))
def autorange(a,b, step=1):
    if a == b:return (a,)
    s = sign(a, b, step)
    return range(a, b+s, s)
def get_ints(line, strip_line=False):
    if strip_line:
        line = line.strip()
    return [*map(int, non_digits.split(line))]

def d10(inp, sample=False):
    p1, p2 = 0, None

    points1 = {'}': 1197, ')': 3, ']': 57, '>': 25137}
    points2 = {'(':1, '[':2, '{':3, '<':4}
    # Matched brackets
    bal = re.compile(r'\(\)|\{\}|<>|\[\]')
    # The first closing bracket after a pile of opening brackets
    unbal = re.compile(r'[({[<]*([)\]}>])')
    p2a = []
    for line in inp.strip().split():
        old = ''
        while line and line != old:
            old = line
            line = bal.sub('', line)
        m = unbal.search(line)
        if m is not None:
            p1 += points1[m.group(1)]
        else:
            p2v = 0
            for char in reversed(line):
                p2v *= 5
                p2v += points2[char]
            p2a.append(p2v)
    # Only happens in test cases
    if len(p2a) == 0:
        return p1, None

    p2a.sort()
    mid_num = len(p2a) // 2
    p2 = p2a[mid_num]

    return p1, p2

def validate_test(case_id, inp=None, want_p1=None, want_p2=None):
    do_p1, do_p2 = False, False
    #print(f"validate_test({case_id}, {inp}, {want_p1}, {want_p2})")
    got_p1, got_p2 = d10(inp, sample=True)
    if want_p1 is not None:
        assert want_p1 == got_p1, f"{case_id=} p1:\n\t{want_p1=}\n\t{got_p1=}"
        do_p1 = True
    if want_p2 is not None:
        assert want_p2 == got_p2, f"{case_id=} p2:\n\t{want_p2=}\n\t{got_p2=}"
        do_p2 = True
    return True, do_p1, do_p2

def main():
    with open('../inputs/d10.txt') as f:
        inp = f.read().strip()
    return d10(inp)

if __name__ == '__main__':
    cases = [
        #(id, inp, p1, p2),
        (0, """[({(<(())[]>[[{[]{<()<>>
[(()[<>])]({[<{<<[]>>(
{([(<{}[<>[]}>{[]{[(<()>
(((({<>}<{<{<>}{[]{[]{}
[[<[([]))<([[{}[[()]]]
[{[{({}]{}}([{[{{{}}([]
{<[[]]>}<{[{[{[]{()[[[]
[<(<(<(<{}))><([]([]()
<{([([[(<>()){}]>(<<{{
<{([{{}}[<[[[<>{}]]]>[]]""", 26397, 288957),
        (1, """{([(<{}[<>[]}>{[]{[(<()>""", 1197, None),
    ]

    """
    # Non multiprocessing version
    for case in cases:
        validate_test(*case)
    p1, p2 = main()
    print(f"p1 = {p1}\np2 = {p2}")
    """


    with Pool(processes=min(8, len(cases) + 1)) as pool:
        main_res = pool.apply_async(main)
        test_res = [pool.apply_async(validate_test, case) for case in cases]
        test_pass, do_p1, do_p2 = True, False, False
        for test in test_res:
            tp, dp1, dp2 = test.get(30)
            test_pass &= tp
            do_p1 |= dp1
            do_p2 |= dp2
        if test_pass:
            p1, p2 = main_res.get(60)
            assert do_p1 or do_p2, "Didn't run any tets"
            assert p1 is None or do_p1 == True, "Got P1 value without 'do_p1' set"
            assert p2 is None or do_p2 == True, "Got P2 value without 'do_p2' set"
            print(f"p1 = {p1}\np2 = {p2}")

from collections import defaultdict, Counter, deque
from itertools import product
from multiprocessing import Pool
import math
import re

non_digits = re.compile('[^0-9]+')
def sign(a, b, step=1):
    return int(math.copysign(step, b-a))
def autorange(a,b, step=1):
    if a == b:return (a,)
    s = sign(a, b, step)
    return range(a, b+s, s)
def get_ints(line, strip_line=False):
    if strip_line:
        line = line.strip()
    return [*map(int, non_digits.split(line))]

def d10(inp, sample=False):
    p1, p2 = 0, None

    points1 = {'}': 1197, ')': 3, ']': 57, '>': 25137}
    points2 = {'(':1, '[':2, '{':3, '<':4}
    # Matched brackets
    bal = re.compile(r'\(\)|\{\}|<>|\[\]')
    # The first closing bracket after a pile of opening brackets
    unbal = re.compile(r'[({[<]*([)\]}>])')
    p2a = []
    for line in inp.strip().split():
        old = ''
        while line and line != old:
            old = line
            line = bal.sub('', line)
        m = unbal.search(line)
        if m is not None:
            p1 += points1[m.group(1)]
        else:
            p2v = 0
            for char in reversed(line):
                p2v *= 5
                p2v += points2[char]
            p2a.append(p2v)
    # Only happens in test cases
    if len(p2a) == 0:
        return p1, None

    p2a.sort()
    mid_num = len(p2a) // 2
    p2 = p2a[mid_num]

    return p1, p2

def validate_test(case_id, inp=None, want_p1=None, want_p2=None):
    do_p1, do_p2 = False, False
    #print(f"validate_test({case_id}, {inp}, {want_p1}, {want_p2})")
    got_p1, got_p2 = d10(inp, sample=True)
    if want_p1 is not None:
        assert want_p1 == got_p1, f"{case_id=} p1:\n\t{want_p1=}\n\t{got_p1=}"
        do_p1 = True
    if want_p2 is not None:
        assert want_p2 == got_p2, f"{case_id=} p2:\n\t{want_p2=}\n\t{got_p2=}"
        do_p2 = True
    return True, do_p1, do_p2

def main():
    with open('../inputs/d10.txt') as f:
        inp = f.read().strip()
    return d10(inp)

if __name__ == '__main__':
    cases = [
        #(id, inp, p1, p2),
        (0, """[({(<(())[]>[[{[]{<()<>>
[(()[<>])]({[<{<<[]>>(
{([(<{}[<>[]}>{[]{[(<()>
(((({<>}<{<{<>}{[]{[]{}
[[<[([]))<([[{}[[()]]]
[{[{({}]{}}([{[{{{}}([]
{<[[]]>}<{[{[{[]{()[[[]
[<(<(<(<{}))><([]([]()
<{([([[(<>()){}]>(<<{{
<{([{{}}[<[[[<>{}]]]>[]]""", 26397, 288957),
        (1, """{([(<{}[<>[]}>{[]{[(<()>""", 1197, None),
    ]

    """
    # Non multiprocessing version
    for case in cases:
        validate_test(*case)
    p1, p2 = main()
    print(f"p1 = {p1}\np2 = {p2}")
    """


    with Pool(processes=min(8, len(cases) + 1)) as pool:
        main_res = pool.apply_async(main)
        test_res = [pool.apply_async(validate_test, case) for case in cases]
        test_pass, do_p1, do_p2 = True, False, False
        for test in test_res:
            tp, dp1, dp2 = test.get(30)
            test_pass &= tp
            do_p1 |= dp1
            do_p2 |= dp2
        if test_pass:
            p1, p2 = main_res.get(60)
            assert do_p1 or do_p2, "Didn't run any tets"
            assert p1 is None or do_p1 == True, "Got P1 value without 'do_p1' set"
            assert p2 is None or do_p2 == True, "Got P2 value without 'do_p2' set"
            print(f"p1 = {p1}\np2 = {p2}")

# Matched brackets # The first closing bracket after a pile of opening brackets # Only happens in test cases #print(f"validate_test({case_id}, {inp}, {want_p1}, {want_p2})") #(id, inp, p1, p2), [({(<(())[]>[[{[]{<()<>> [(()[<>])]({[<{<<[]>>( {([(<{}[<>[]}>{[]{[(<()> (((({<>}<{<{<>}{[]{[]{} [[<[([]))<([[{}[[()]]] [{[{({}]{}}([{[{{{}}([] {<[[]]>}<{[{[{[]{()[[[] [<(<(<(<{}))><([]([]() <{([([[(<>()){}]>(<<{{ <{([{{}}[<[[[<>{}]]]>[]] {([(<{}[<>[]}>{[]{[(<()> # Non multiprocessing version     for case in cases:         validate_test(*case)     p1, p2 = main()     print(f"p1 = {p1}\np2 = {p2}")