"""Classes of best layouts for n = 28..47 that contain a whole 27-card third.

classify.py shows every best layout contains one for n = 28..40 and 45..47.
Such a layout is H + A + B: the third H, a cards A in one parallel third and
b cards B in the other, with 117 + g(a) + g(b) + ab sets, so A and B must be
best a- and b-card subsets and (a, b) a best split. An affine map can take H
to {4th digit 0}, put the larger part in {4th digit 1}, and then move A to any
equivalent subset there while B stays a best subset. So it's enough to try A
from one per class (classes3 dump) and B from all best subsets, and count the
distinct canonical keys (canon4).

  python3 classes4.py <path to classes3> <path to canon4> > out/classes4.txt
"""
import subprocess, sys
from itertools import combinations

G = [0, 0, 0, 1, 1, 2, 3, 5, 8, 12, 12, 13, 14, 16, 19, 23, 26, 30, 36, 41, 47,
     54, 62, 71, 81, 92, 104, 117]
classes3, canon4 = sys.argv[1], sys.argv[2]


def best_subsets(k):
    """(all best k-subsets of a third, one per class), as lists of points 0..26."""
    if k < 3:
        every = [list(c) for c in combinations(range(27), k)]
        return every, [list(range(k))]
    out = subprocess.run([classes3, 'dump', str(k)], capture_output=True, text=True).stdout.split('\n')
    pts = lambda m: [p for p in range(27) if m >> p & 1]
    every = [pts(int(l.split()[1])) for l in out if l.startswith('all')]
    reps = [pts(int(l.split()[1])) for l in out if l.startswith('rep')]
    return every, reps


cache = {}
for n in range(28, 48):
    m = n - 27
    top = max(G[a] + G[m - a] + a * (m - a) for a in range(m + 1))
    pairs = sorted({(max(a, m - a), min(a, m - a)) for a in range(m + 1) if G[a] + G[m - a] + a * (m - a) == top})
    layouts = []
    for a, b in pairs:
        for k in (a, b):
            if k not in cache:
                cache[k] = best_subsets(k)
        for A in cache[a][1]:
            for B in cache[b][0]:
                row = ['1'] * 27 + ['0'] * 54  # 4th digit 0: the whole third
                for p in A: row[27 + p] = '1'
                for p in B: row[54 + p] = '1'
                layouts.append(''.join(row))
    res = subprocess.run([canon4], input='\n'.join(layouts) + '\n', capture_output=True, text=True).stdout.split('\n')
    keys = {}
    for r in res:
        if r:
            key, k, sets, full = r.split()
            assert int(k) == n and int(sets) == 117 + top
            keys[key] = int(full)
    by_full = {}
    for f in keys.values():
        by_full[f] = by_full.get(f, 0) + 1
    print(f'n={n} sets={117 + top} splits={pairs} candidates={len(layouts)} classes={len(keys)} '
          f'(by whole thirds contained: {dict(sorted(by_full.items()))})', flush=True)
