# -*- coding: utf-8 -*-
"""
THE TWENTY-NINE-OPENING TEST — rules frozen before this file was run.

Protocol (written down first, then executed once, results reported whatever they are):
  1. Text: the Uthmani text (quran-json encoding of the Cairo/KFGQPC rasm) under the
     chapter's primary convention T8 with Rule 2 (tashkil stripped; chair of ئ = yāʾ;
     ة = hāʾ; no dagger alif; no hamza seats counted as alif). Basmala included for
     every sūrah in scope.
  2. Cases: every opening is a case. Openings with IDENTICAL letters are ONE case and
     are summed. Nothing else is summed. Ḥā-Mīm is verse 1 of all seven sūrahs 40–46
     (in 42 it is verse 1 with ʿAyn-Sīn-Qāf as verse 2), so 42 belongs to the Ḥā-Mīm
     case AND forms its own ʿAyn-Sīn-Qāf case. The sum without 42 is reported beside it.
  3. Count: every occurrence of each of the case's letters across its sūrah(s).
  4. Rule 8 (written vs recited consonant): the page count is primary; where a
     documented reading changes a counted consonant (7:69 basṭatan, sīn for ṣād in
     Ḥafṣ; 68:1 نون per Ibn ʿAbbās) the recited count is reported beside it. A case
     "holds" only if the PAGE count is a multiple of 19.
  5. Sign classification, BOTH rules, fixed here:
       formula-only: a case is a sign case if any opening in it writes tilka āyāt
                     (openings 10, 12, 13, 15, 26, 27, 28, 31).
       letter-group: a case is a sign case if its letter-group is named as signs in
                     any opening (A-L-M, A-L-R, A-L-M-R, Ṭ-S-M, Ṭ-S), extended to
                     A-L-M-Ṣ which carries the A-L-M group.
  6. Statistics: (a) Fisher's exact test, one-tailed, sign-vs-hold, under each rule,
     no set-asides; (b) binomial tail P(X >= observed holds | n cases, p = 1/19);
     (c) per-case calibrated p from Appendix D where available.
No rule below was altered after the numbers were seen.
"""
import sys, io, contextlib, importlib.util
from math import comb

buf = io.StringIO()
with contextlib.redirect_stdout(buf):
    spec = importlib.util.spec_from_file_location('c19', 'count19.py')
    m = importlib.util.module_from_spec(spec); sys.argv = ['count19.py']
    try: spec.loader.exec_module(m)
    except SystemExit: pass

CASES = [
    ('A-L-M',       [2, 3, 29, 30, 31, 32], ['alif', 'l', 'm']),
    ('A-L-M-Ṣ',     [7],                    ['alif', 'l', 'm', 'sad']),
    ('A-L-R',       [10, 11, 12, 14, 15],   ['alif', 'l', 'r']),
    ('A-L-M-R',     [13],                   ['alif', 'l', 'm', 'r']),
    ('K-H-Y-ʿA-Ṣ',  [19],                   ['k', 'h', 'ya', 'ayn', 'sad']),
    ('Ṭ-H',         [20],                   ['ta', 'h']),
    ('Ṭ-S-M',       [26, 28],               ['ta', 'sin', 'm']),
    ('Ṭ-S',         [27],                   ['ta', 'sin']),
    ('Y-S',         [36],                   ['ya', 'sin']),
    ('Ṣ',           [38],                   ['sad']),
    ('Ḥ-M',         [40, 41, 42, 43, 44, 45, 46], ['ha', 'm']),
    ('ʿA-S-Q',      [42],                   ['ayn', 'sin', 'q']),
    ('Q',           [50],                   ['q']),
    ('N',           [68],                   ['n']),
]
FORMULA_A = {10, 12, 13, 15, 26, 27, 28, 31}
LETTER_GROUP_SIGN = {'A-L-M', 'A-L-M-Ṣ', 'A-L-R', 'A-L-M-R', 'Ṭ-S-M', 'Ṭ-S'}
RECITED = {'A-L-M-Ṣ': -1, 'N': +1}   # 7:69 sīn for ṣād; 68:1 نون

def total(suras, letters):
    text = ''.join(m.BASMALA + m.SURA[s] for s in suras)
    return sum(m.count(text, L, 'T8') for L in letters)

rows = []
for name, suras, letters in CASES:
    n = total(suras, letters)
    rows.append(dict(name=name, suras=suras, letters=letters, page=n, rem=n % 19, holds=(n % 19 == 0),
                     recited=(n + RECITED[name]) if name in RECITED else None,
                     sign_formula=any(s in FORMULA_A for s in suras),
                     sign_group=name in LETTER_GROUP_SIGN))
hm_no42 = total([40, 41, 43, 44, 45, 46], ['ha', 'm'])

def fisher_less(a, b, c, d):
    """P(sign-holds <= a) under hypergeometric; table [[a,b],[c,d]] = [[sign hold, sign fail],[non hold, non fail]]"""
    N = a + b + c + d; K = a + b; n = a + c
    return sum(comb(K, k) * comb(N - K, n - k) for k in range(0, a + 1)) / comb(N, n)

def binom_tail(n, k, p):
    return sum(comb(n, j) * p**j * (1 - p)**(n - j) for j in range(k, n + 1))

print(f"{'case':<12}{'sūrahs':<26}{'page':>8}{'mod19':>6}{'holds':>7}{'recited':>9}{'signA':>7}{'signG':>7}")
for r in rows:
    print(f"{r['name']:<12}{','.join(map(str, r['suras'])):<26}{r['page']:>8}{r['rem']:>6}{'✓' if r['holds'] else '✗':>7}"
          f"{(str(r['recited']) if r['recited'] else '—'):>9}{'S' if r['sign_formula'] else '·':>7}{'S' if r['sign_group'] else '·':>7}")
print(f"\nḤ-M without 42: {hm_no42} (mod 19 = {hm_no42 % 19})")
holds = sum(r['holds'] for r in rows); n = len(rows)
print(f"\nholds: {holds} of {n}; binomial tail P(X>={holds} | n={n}, p=1/19) = {binom_tail(n, holds, 1/19):.4g} (1 in {1/binom_tail(n, holds, 1/19):.0f})")
for key, label in (('sign_group', 'letter-group rule'), ('sign_formula', 'formula-only rule')):
    a = sum(r['holds'] and r[key] for r in rows); b = sum((not r['holds']) and r[key] for r in rows)
    c = sum(r['holds'] and not r[key] for r in rows); d = sum((not r['holds']) and not r[key] for r in rows)
    p = fisher_less(a, b, c, d)
    print(f"{label}: sign hold/fail = {a}/{b}; non-sign hold/fail = {c}/{d}; Fisher one-tailed = {p:.4g} (1 in {1/p:.0f})")
