#!/usr/bin/env python3
"""Derive operand roles + effects for every opcode from the harness traces."""
import collections, json, sys

PATH = sys.argv[1] if len(sys.argv) > 1 else '/tmp/opencode/exec8.txt'


def load(path):
    tests = {}
    cur = None
    for line in open(path, encoding='latin-1'):
        f = line.rstrip('\n').split('\t')
        if f[0] == 'B':
            cur = (f[2], f[3])
            tests[cur] = {'id': int(f[1]), 'ev': []}
        elif f[0] == 'E' and cur:
            tests[cur]['ev'].append(f[2:])
    return tests


def segments(ev):
    seg = collections.OrderedDict()
    pc = None
    for e in ev:
        if e[0] == 'PC':
            pc = int(e[1])
            seg.setdefault(pc, [])
        else:
            seg.setdefault(pc, []).append(e)
    return seg


def events(ev):
    out = {'reads': [], 'writes': [], 'k': [], 'sidx': [], 'idx': [],
           'ops': [], 'state': [], 'err': None, 'call_args': None}
    for e in ev:
        k = e[0]
        try:
            if k == 'R':
                out['reads'].append(int(e[1]))
            elif k == 'W':
                out['writes'].append((int(e[1]), e[2]))
            elif k in ('K7', 'K8', 'K9'):
                out['k'].append((k, int(e[1])))
            elif k == 'SIDX':
                out['sidx'].append((e[1], e[2], e[3]))
            elif k == 'IDX':
                out['idx'].append((e[1], e[2]))
            elif k == 'OP':
                out['ops'].append(e[1])
            elif k == 'S':
                out['state'].append(int(e[1]))
            elif k == 'CALL':
                out['call_args'] = int(e[2])
            elif k == 'ERR':
                out['err'] = e[1] if len(e) > 1 else '?'
        except (ValueError, IndexError):
            pass
    return out


def role(idx, base):
    """base maps A/B/C to their numeric value in that run."""
    a, b, c = base
    if idx == a:
        return 'A'
    if idx == b:
        return 'B'
    if idx == c:
        return 'C'
    if idx > a:
        return 'A+%d' % (idx - a)
    return '?%d' % idx


def align_roles(seq1, seq2, seq3, bases):
    """align three runs by position and derive the role of each index"""
    roles = []
    n = min(len(seq1), len(seq2), len(seq3))
    for i in range(n):
        i1, i2, i3 = seq1[i], seq2[i], seq3[i]
        a1, b1, c1 = bases[0]
        a2, b2, c2 = bases[1]
        a3, b3, c3 = bases[2]
        if i2 - i1 == a2 - a1:
            roles.append('A+%d' % (i1 - a1) if i1 - a1 else 'A')
        elif i3 - i1 == b3 - b1:
            roles.append('B+%d' % (i1 - b1) if i1 - b1 else 'B')
        elif i1 - c1 >= 0:
            roles.append('C+%d' % (i1 - c1) if i1 - c1 else 'C')
        else:
            roles.append('?%d' % i1)
    return roles


def staging_from(cons):
    """infer (i, d, c) delta from the consumer (op 83) run"""
    st = {'i': None, 'd': None, 'c': None}
    if cons['writes']:
        k, v = cons['writes'][0]
        st['i'] = 'q'
        st['d'] = {5: 'C', 1: 'A', 2: 'B'}.get(k, '#%d' % k)
        st['c'] = {'P:r4': '-', 'P:r1': 'q[A]', 'P:K7': 'K7', 'P:K8': 'K8',
                   'P:K9': 'K9', 'P:tbl': 'q'}.get(v, v)
    for sidx in cons['sidx']:
        tgt, key, val = sidx
        st['i'] = 'q[%s]' % tgt[2:] if tgt.startswith('r') else tgt
        st['d'] = {5: 'C', 1: 'A', 2: 'B'}.get(int(key), '#%s' % key) if key.isdigit() else key
        st['c'] = {'P:r4': '-', 'P:r1': 'q[A]', 'P:tbl': 'q'}.get(val, val)
    return st


def main():
    tests = load(PATH)
    ops = sorted({int(l[0][1:]) for l in tests if l[0][:1] in 'SVW'})
    out = {}
    for op in ops:
        base = {}
        for lab, arr in (('S', []), ('V', []), ('W', [])):
            t = tests.get((lab + str(op), 'proxy'))
            if not t:
                continue
            seg = segments(t['ev'])
            pcs = [k for k in seg if isinstance(k, int)]
            first = pcs[0] if pcs else None
            base[lab] = {
                'ev': events(seg.get(first, [])),
                'next': pcs[1] if len(pcs) > 1 else None,
            }
        if 'S' not in base:
            continue
        s, v, w = base['S'], base['V'], base['W']
        roles_read = align_roles(s['ev']['reads'], v['ev']['reads'], w['ev']['reads'],
                                 [(1, 2, 3), (6, 2, 5), (1, 7, 3)])
        roles_write = align_roles([k for k, _ in s['ev']['writes']],
                                  [k for k, _ in v['ev']['writes']],
                                  [k for k, _ in w['ev']['writes']],
                                  [(1, 2, 3), (6, 2, 5), (1, 7, 3)])
        staging = None
        p = tests.get(('P' + str(op), 'proxy'))
        if p:
            seg = segments(p['ev'])
            staging = staging_from(events(seg.get(4, [])))
        q = tests.get(('Q' + str(op), 'proxy'))
        qeff = None
        if q:
            seg = segments(q['ev'])
            qeff = events(seg.get(4, []))
        out[op] = {
            'pc_next': s['next'],
            'reads': roles_read,
            'writes': roles_write,
            'write_vals': [val for _, val in s['ev']['writes']],
            'k': sorted({k for k, _ in s['ev']['k']}),
            'idx': s['ev']['idx'],
            'sidx': s['ev']['sidx'],
            'ops': s['ev']['ops'],
            'state': s['ev']['state'],
            'err': s['ev']['err'],
            'err_num': v['ev']['err'],
            'err_str': w['ev']['err'],
            'staging': staging,
            'q_consumer': qeff,
            'numeric_write': [(k, val) for k, val in
                              [(kk, vv) for kk, vv in []]],
        }
    json.dump({str(k): v for k, v in out.items()},
              open('/tmp/opencode/roles.json', 'w'), indent=0)
    for op in ops:
        r = out[op]
        st = r['staging'] or {}
        print('%3d pc=%-4s rd[%s] wr[%s] k[%s] st[%s] ops[%s] stage{i=%s d=%s c=%s} err[%s]'
              % (op, r['pc_next'], ','.join(r['reads']), ','.join(r['writes']),
                 ','.join(r['k']), ','.join(str(x) for x in r['state']),
                 ','.join(r['ops']), st.get('i'), st.get('d'), st.get('c'),
                 (r['err'] or '')[:26]))


if __name__ == '__main__':
    main()
