#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Root & Branch Register — independent verifier.

This script is the whole of what a mirror needs. It reads a directory of
Register files and a published digest, recomputes the Merkle root from the
files alone, and reports whether they agree. It imports nothing but the Python
standard library, contacts nothing, and does not use the Registrar's software
(Transmission Standard v0.10 §17.1, §17.2).

    python3 verify.py <export-directory> <digest-file>

Exit status is 0 when the root agrees, 1 when it does not.
"""
import hashlib, os, re, sys

def sha(p):
    return hashlib.sha256(open(p, "rb").read()).hexdigest()

def merkle(hexes):
    if not hexes:
        return "0" * 64
    lvl = [bytes.fromhex(h) for h in hexes]
    while len(lvl) > 1:
        lvl = [hashlib.sha256(lvl[i] + (lvl[i + 1] if i + 1 < len(lvl) else lvl[i])).digest()
               for i in range(0, len(lvl), 2)]
    return lvl[0].hex()

def collect(root):
    out = []
    for kind in ("entry", "record", "practice", "work", "observation", "reconstruction"):
        d = os.path.join(root, kind)
        if not os.path.isdir(d):
            continue
        for fn in sorted(os.listdir(d)):
            if fn.endswith(".txt"):
                out.append((kind + "/" + fn[:-4], os.path.join(d, fn)))
    return out

def main(argv):
    if len(argv) != 3:
        print(__doc__.strip()); return 2
    export, digest_file = argv[1], argv[2]
    published_root, published = None, []
    for line in open(digest_file, encoding="utf-8"):
        m = re.match(r"^root\s*:\s*([0-9a-f]{64})\s*$", line)
        if m: published_root = m.group(1)
        m = re.match(r"^\s{2}([0-9a-f]{64})\s{2}(\S+)\s*$", line)
        if m: published.append((m.group(2), m.group(1)))
    if not published_root:
        print("No root found in " + digest_file); return 2

    found = collect(export)
    computed = [(n, sha(p)) for n, p in found]
    root = merkle([h for _n, h in computed])

    print("files on disk   : %d" % len(computed))
    print("leaves published: %d" % len(published))
    print("root computed   : " + root)
    print("root published  : " + published_root)

    bad = 0
    pub = dict(published)
    for name, h in computed:
        if name not in pub:
            print("  NOT IN DIGEST   " + name); bad += 1
        elif pub[name] != h:
            print("  HASH DIFFERS    " + name); bad += 1
    for name, _h in published:
        if name not in dict(computed):
            print("  MISSING FILE    " + name); bad += 1

    if root == published_root and not bad:
        print("\nThe export matches the published digest.")
        return 0
    print("\nThe export does NOT match the published digest.")
    return 1

if __name__ == "__main__":
    sys.exit(main(sys.argv))
