"""Generic durable sweep runner.

A job lives in a directory with todo/, claim/, done/ chunk files and a
plant.json.  Every worker tests each ordering against 128 legal twelfth words
and all 33 derivation paths, and looks for two addresses: the real puzzle
wallet and a planted one.  The planted seed is a genuine task inside the queue,
so a run that finishes without reporting the plant has proved itself broken.
"""
import os, sys, json, time, random
HITFILE="RECOVERED_HITS.txt"
import sweeplib as S

def claim(root):
    try:
        todo = os.listdir(root + "/todo")
    except FileNotFoundError:
        return None
    random.shuffle(todo)
    for f in todo:
        try:
            os.rename(root + "/todo/" + f, root + "/claim/" + f)
            return f
        except OSError:
            continue
    return None

def main():
    root = sys.argv[1]; wid = sys.argv[2]
    job = os.path.basename(root.rstrip("/"))
    plant = json.load(open(root + "/plant.json"))
    targets = {S.TARGET, plant["addr"]}
    nd = 0; nc = 0; t0 = time.time(); nplant = 0
    while True:
        if os.path.exists(HITFILE):
            print("  w%s: HIT FILE PRESENT - stopping" % wid, flush=True); break
        f = claim(root)
        if not f:
            break
        try:
            tasks = json.load(open(root + "/claim/" + f))
        except (FileNotFoundError, ValueError):
            # another process requeued this chunk between the claim and the read
            print("  w%s: chunk %s vanished after claim, skipping" % (wid, f), flush=True)
            continue
        hits = []
        for label, w11 in tasks:
            for mn in S.endings(w11):
                nd += 1
                S.walk(S.seed(mn), targets, hits)
                if hits:
                    for pname, addr in hits:
                        if addr == plant["addr"]:
                            nplant += 1
                            print("  [plant seen] %s %s" % (label, pname), flush=True)
                        else:
                            line = S.record_hit(job, label, mn, pname, addr)
                            print("\n*** MATCH *** %s" % line, flush=True)
                            os._exit(99)
                    hits = []
        # Completion is recorded by writing a new file into done/ and then
        # unlinking the claim, never by renaming.  A rename fails outright when
        # something else has already moved the claim file, and that killed seven
        # of eight workers on the first attempt at this job.
        try:
            open(root + "/done/" + f, "w").write(json.dumps(tasks))
        except OSError as e:
            print("  w%s: could not mark %s done: %s" % (wid, f, e), flush=True)
        try:
            os.unlink(root + "/claim/" + f)
        except OSError:
            pass
        nc += 1
        el = time.time() - t0
        if nc % 5 == 0 or nc == 1:
            left = len(os.listdir(root + "/todo"))
            print("  w%s: %d chunks, %d left, %d seeds, %.1fm, %.0f/s" %
                  (wid, nc, left, nd, el / 60, nd / max(el, 1)), flush=True)
    print("WORKER %s DONE job=%s chunks=%d seeds=%d plant_hits=%d" %
          (wid, job, nc, nd, nplant), flush=True)

if __name__ == "__main__":
    main()
