"""Draw the three structure figures of simd-hash.md (hand-laid-out SVG).

Usage: python draw_figures.py   (writes ../xxh3-accumulate-lanes.svg,
       ../xxh3-block-secret.svg, ../wyhash-three-chains.svg)
"""
import pathlib
OUT = pathlib.Path(__file__).resolve().parent.parent


def draw_acc():
    W,H=980,600
    o=[]
    a=o.append
    a(f'<svg xmlns="http://www.w3.org/2000/svg" width="{W}" height="{H}" viewBox="0 0 {W} {H}" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace">')
    a('<defs><marker id="ar" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#768390"/></marker>'
      '<marker id="ao" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#f0883e"/></marker>'
      '<marker id="ag" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#3fb950"/></marker></defs>')
    a(f'<rect width="{W}" height="{H}" fill="#2d333b"/>')
    a('<text x="490" y="30" text-anchor="middle" fill="#cdd9e5" font-size="17" font-weight="bold">XXH3_accumulate_512: one 64-byte stripe, 8 lanes x 64 bit</text>')
    X0,LW,G=190,86,11
    def lx(i): return X0+i*(LW+G)
    def label(y,t1,t2=None):
        a(f'<text x="176" y="{y+16 if t2 is None else y+11}" text-anchor="end" fill="#adbac7" font-size="13">{t1}</text>')
        if t2: a(f'<text x="176" y="{y+27}" text-anchor="end" fill="#768390" font-size="11">{t2}</text>')
    def box(x,y,w,h,stroke,fill,txt,tc="#cdd9e5",fs=13):
        a(f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="4" fill="{fill}" stroke="{stroke}" stroke-width="1.5"/>')
        a(f'<text x="{x+w/2}" y="{y+h/2+5}" text-anchor="middle" fill="{tc}" font-size="{fs}">{txt}</text>')
    def arrow(x1,y1,x2,y2,m="ar",c="#768390"):
        a(f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" stroke="{c}" stroke-width="1.4" marker-end="url(#{m})"/>')
    y1,y2,y3,y4,y5=60,120,190,265,350
    label(y1,"input","64 B stripe")
    label(y2,"secret","+ 8 bytes * n")
    label(y3,"dk = d ^ k")
    label(y4,"p | swap(d)","lo32*hi32 | d[i^1]")
    label(y5,"acc[i] +=","p + d[i^1]")
    for i in range(8):
        x=lx(i)
        box(x,y1,LW,32,"#636e7b","#22272e",f"d{i}")
        box(x+24,y2,LW-24,32,"#a371f7","#2f2a3d",f"k{i}")
        arrow(x+10,y1+32,x+10,y3-3)
        arrow(x+24+(LW-24)/2,y2+32,x+24+(LW-24)/2,y3-3)
        box(x,y3,LW,32,"#636e7b","#22272e",f"dk{i}")
        hw=(LW-6)/2
        box(x,y4,hw,32,"#f0883e","#3a2f24",f"p{i}")
        box(x+hw+6,y4,hw,32,"#3fb950","#233327",f"d{i^1}")
        arrow(x+hw/2,y3+32,x+hw/2,y4-3,"ao","#f0883e")
        arrow(x+hw/2,y4+32,x+LW/2-8,y5-3,"ao","#f0883e")
        arrow(x+hw+6+hw/2,y4+32,x+LW/2+8,y5-3,"ag","#3fb950")
        box(x,y5,LW,34,"#388bfd","#1f2d44",f"acc{i}")
    # register grouping
    def bracket(i0,i1,y,c,txt):
        xa=lx(i0); xb=lx(i1)+LW
        a(f'<path d="M{xa},{y} L{xa},{y+8} L{xb},{y+8} L{xb},{y}" fill="none" stroke="{c}" stroke-width="1.6"/>')
        a(f'<text x="{(xa+xb)/2}" y="{y+25}" text-anchor="middle" fill="{c}" font-size="12">{txt}</text>')
    yb=410
    a(f'<text x="176" y="{yb+22}" text-anchor="end" fill="#adbac7" font-size="13">AVX-512</text>')
    bracket(0,7,yb,"#cdd9e5","1 x __m512i (zmm), 1 iteration per stripe")
    yb+=44
    a(f'<text x="176" y="{yb+22}" text-anchor="end" fill="#adbac7" font-size="13">AVX2</text>')
    bracket(0,3,yb,"#388bfd","__m256i (ymm) #0"); bracket(4,7,yb,"#388bfd","__m256i (ymm) #1")
    yb+=44
    a(f'<text x="176" y="{yb+22}" text-anchor="end" fill="#adbac7" font-size="13">SSE2</text>')
    for j in range(4): bracket(2*j,2*j+1,yb,"#a371f7",f"xmm #{j}")
    yb+=44
    a(f'<text x="176" y="{yb+16}" text-anchor="end" fill="#adbac7" font-size="13">NEON</text>')
    a(f'<text x="176" y="{yb+30}" text-anchor="end" fill="#768390" font-size="11">AArch64, non-Apple</text>')
    bracket(0,5,yb,"#3fb950","3 x uint64x2_t (NEON)"); bracket(6,7,yb,"#f0883e","scalar")
    a('</svg>')
    open(OUT / 'xxh3-accumulate-lanes.svg', 'w').write('\n'.join(o))

def draw_block():
    W,H=980,470
    o=[];a=o.append
    a(f'<svg xmlns="http://www.w3.org/2000/svg" width="{W}" height="{H}" viewBox="0 0 {W} {H}" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace">')
    a(f'<rect width="{W}" height="{H}" fill="#2d333b"/>')
    a('<text x="490" y="30" text-anchor="middle" fill="#cdd9e5" font-size="17" font-weight="bold">XXH3 long input (len &gt; 240): stripes, blocks, 192-byte default secret</text>')
    def t(x,y,s,c="#adbac7",fs=12,anc="middle"): a(f'<text x="{x}" y="{y}" text-anchor="{anc}" fill="{c}" font-size="{fs}">{s}</text>')
    def rect(x,y,w,h,st,fi,sw=1.4,dash=None):
        d=f' stroke-dasharray="{dash}"' if dash else ''
        a(f'<rect x="{x}" y="{y}" width="{w}" height="{h}" fill="{fi}" stroke="{st}" stroke-width="{sw}"{d}/>')
    # ---- input row ----
    t(40,62,"input",anc="start",c="#cdd9e5",fs=13)
    y=72; h=34
    bx=[40,300,560]; bw=240
    for j,x in enumerate(bx[:2]):
        rect(x,y,bw,h,"#388bfd","#1f2d44")
        for k in range(1,16):
            xx=x+k*bw/16
            a(f'<line x1="{xx}" y1="{y}" x2="{xx}" y2="{y+h}" stroke="#388bfd" stroke-width="0.7" opacity="0.7"/>')
        t(x+bw/2,y+h+18,f"block {j}: 16 stripes x 64 B = 1024 B",fs=11)
    # partial block
    px=560; pw=6*bw/16
    rect(px,y,pw,h,"#388bfd","#1f2d44")
    for k in range(1,6):
        xx=px+k*bw/16
        a(f'<line x1="{xx}" y1="{y}" x2="{xx}" y2="{y+h}" stroke="#388bfd" stroke-width="0.7" opacity="0.7"/>')
    rect(px+pw,y,50,h,"#636e7b","#22272e")
    t(px+pw/2+25,y+h+18,"last partial block",fs=11)
    t(px+pw/2+25,y+h+32,"(len-1-1024*nb)/64 stripes",fs=11,c="#768390")
    # last stripe overlapping
    end=px+pw+50
    rect(end-bw/16*1.6,y-8,bw/16*1.6,h+16,"#f85149","none",sw=1.8,dash="4 3")
    t(end+8,y+14,"last stripe:",anc="start",c="#f85149",fs=11)
    t(end+8,y+28,"input[len-64, len)",anc="start",c="#f85149",fs=11)
    t(end+8,y+42,"(may overlap)",anc="start",c="#f85149",fs=11)
    # scramble markers
    for x in (300,560):
        a(f'<line x1="{x-10}" y1="{y-18}" x2="{x-10}" y2="{y+h+2}" stroke="#f0883e" stroke-width="2.2"/>')
        t(x-10,y-22,"scramble",c="#f0883e",fs=11)
    # ---- secret row ----
    sy=250; sx=100; ppb=4  # px per byte
    t(40,sy-100,"secret (192 B)",anc="start",c="#cdd9e5",fs=13)
    rect(sx,sy,192*ppb,30,"#a371f7","#2f2a3d")
    for b in (0,64,128,192):
        X=sx+b*ppb
        a(f'<line x1="{X}" y1="{sy+30}" x2="{X}" y2="{sy+38}" stroke="#768390"/>')
        t(X,sy+52,str(b),c="#768390",fs=11)
    def span(b0,b1,yy,c,label,above=True):
        x0=sx+b0*ppb; x1=sx+b1*ppb
        d=6 if above else -6
        a(f'<path d="M{x0},{yy+d} L{x0},{yy} L{x1},{yy} L{x1},{yy+d}" fill="none" stroke="{c}" stroke-width="1.8"/>')
        t((x0+x1)/2,yy-6 if above else yy+16,label,c=c,fs=11)
    span(0,64,sy-72,"#388bfd","stripe 0 key [0,64)")
    span(120,184,sy-72,"#388bfd","stripe 15 key [120,184)")
    span(8,72,sy-40,"#388bfd","stripe 1 [8,72)")
    t(sx+100*ppb,sy-38,"... +8 B per stripe ...",c="#768390",fs=11)
    span(11,75,sy+80,"#3fb950","merge [11,75)",above=False)
    span(128,192,sy+80,"#f0883e","scramble key [128,192)",above=False)
    span(121,185,sy+122,"#f85149","last-stripe key [121,185)",above=False)
    t(490,H-18,"16 stripes per block = (secretSize - 64) / 8;  offsets 121 = 192-64-7 and 11 are deliberately not multiples of 8",c="#768390",fs=11)
    a('</svg>')
    open(OUT / 'xxh3-block-secret.svg', 'w').write('\n'.join(o))

def draw_wy():
    W,H=1000,400
    o=[];a=o.append
    a(f'<svg xmlns="http://www.w3.org/2000/svg" width="{W}" height="{H}" viewBox="0 0 {W} {H}" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace">')
    a('<defs><marker id="ar" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#768390"/></marker></defs>')
    a(f'<rect width="{W}" height="{H}" fill="#2d333b"/>')
    a('<text x="500" y="30" text-anchor="middle" fill="#cdd9e5" font-size="17" font-weight="bold">wyhash final4, len &gt; 48: three independent _wymix chains</text>')
    def t(x,y,s,c="#adbac7",fs=12,anc="middle"): a(f'<text x="{x}" y="{y}" text-anchor="{anc}" fill="{c}" font-size="{fs}">{s}</text>')
    def box(x,y,w,h,st,fi,lines,cols=None):
        a(f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="5" fill="{fi}" stroke="{st}" stroke-width="1.5"/>')
        n=len(lines); y0=y+h/2-(n-1)*8+4
        for i,l in enumerate(lines):
            t(x+w/2,y0+i*16,l,c=(cols[i] if cols else "#cdd9e5"),fs=12)
    def arrow(x1,y1,x2,y2): a(f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" stroke="#768390" stroke-width="1.5" marker-end="url(#ar)"/>')
    rows=[("seed",1,0,"#388bfd"),("see1",2,16,"#3fb950"),("see2",3,32,"#a371f7")]
    ys=[70,150,230]; bh=60
    for (name,si,off,c),y in zip(rows,ys):
        t(60,y+bh/2+5,name,c=c,fs=14,anc="end")
        for r,x in enumerate((80,300)):
            o0=off+48*r
            box(x,y,190,bh,c,"#22272e",[f"{name} = _wymix(",f"p[{o0}:{o0+8}] ^ s[{si}],",f"p[{o0+8}:{o0+16}] ^ {name})"],None)
        arrow(270,y+bh/2,298,y+bh/2)
        arrow(490,y+bh/2,512,y+bh/2)
        t(530,y+bh/2+5,"...",c="#768390",fs=14)
        arrow(550,y+bh/2,578,y+bh/2)
    t(175,62,"round 0 (bytes 0..47)",c="#768390",fs=11)
    t(395,62,"round 1 (bytes 48..95)",c="#768390",fs=11)
    box(580,70,130,220,"#f0883e","#3a2f24",["after loop:","seed ^=","see1 ^ see2"])
    box(760,70,220,100,"#636e7b","#22272e",["tail, while i &gt; 16:","seed = _wymix(p[0:8]^s[1],","p[8:16]^seed)","(one serial chain)"])
    box(760,190,220,100,"#388bfd","#1f2d44",["a,b = last 16 bytes","a^=s[1]; b^=seed;","_wymum(&amp;a,&amp;b)","_wymix(a^s[0]^len, b^s[1])"])
    arrow(710,120,758,120); arrow(870,170,870,188)
    t(500,330,"_wymix(A,B) = lo64(A*B) ^ hi64(A*B), one 64x64 -&gt; 128-bit scalar multiply (x86-64 MUL / MULX, AArch64 MUL + UMULH)",c="#adbac7",fs=11)
    t(500,352,"the three chains read disjoint 16-byte slices and never feed each other inside the loop, so their multiplies overlap",c="#768390",fs=11)
    t(500,374,"s[0..3] = secret (default _wyp); len &lt;= 16 skips the loop and builds a, b from 4-byte or 1..3-byte reads",c="#768390",fs=11)
    a('</svg>')
    open(OUT / 'wyhash-three-chains.svg', 'w').write('\n'.join(o))


if __name__ == "__main__":
    draw_acc()
    draw_block()
    draw_wy()
