"""Rendering geometry only. Colors never feed back into the engine."""
import colorsys
import numpy as np
from PIL import Image, ImageDraw
from review_data import strict_people
from layout import overlay, FONT, IW, IH, intersects
from contract import EDGES


def color(number):
    return tuple(round(v*255) for v in colorsys.hsv_to_rgb((number*.61803398875)%1,.7+(number%3)*.07,.96))


def group_layer(image, groups):
    width,height=image.size
    count=np.zeros((height,width),dtype=np.uint16)
    total=np.zeros((height,width,3),dtype=np.uint32)
    for i,g in enumerate(groups):
        mask=Image.new('L',(width,height),0)
        ImageDraw.Draw(mask).polygon([tuple(p) for p in g['polygon']],fill=1)
        m=np.asarray(mask,dtype=np.uint16)
        count+=m
        total+=m[:,:,None]*np.array(color(i+1),dtype=np.uint32)
    if not groups:
        return image.copy(),0
    avg=total/np.maximum(count,1)[:,:,None]
    original=np.asarray(image,dtype=np.float32)
    alpha=(count>0)[:,:,None]*.18
    rgb=original*(1-alpha)+avg*alpha
    ys,xs=np.indices(count.shape)
    hatch=(count>1)&((xs+ys)%18<3)
    rgb[hatch]=rgb[hatch]*.67+np.array([245,245,245])*.33
    return Image.fromarray(np.clip(rgb,0,255).astype(np.uint8)),int((count>1).sum())


def affected_orders(a):
    if not a['difference']:
        return []
    if a['kind'] in ('T_CHANGE_B_CONTINUES','T_SWAP_B_CONTINUES'):
        return [o for o,x,y in zip(a['orders'],a['previous_ids'],a['current_ids']) if x!=y]
    return a['orders']


def image_panel(image,row,view,palette,kind):
    people=strict_people(row,palette)
    base,overlap=group_layer(image,view['groups']) if kind=='groups' else (image,0)
    panel,meta=overlay(base,people)
    # Match ImageOps.contain integer dimensions used by the original renderer.
    from PIL import ImageOps
    fit=ImageOps.contain(image,(IW,IH))
    sx,sy=fit.width/image.width,fit.height/image.height
    ox,oy=(IW-fit.width)//2,(IH-fit.height)//2
    def pt(p):return (ox+min(fit.width-1,max(0,p[0]*sx)),oy+min(fit.height-1,max(0,p[1]*sy)))
    marks=Image.new('RGBA',(IW,IH),(0,0,0,0));d=ImageDraw.Draw(marks)
    by_order={p['raw_order']:p for p in people}
    bs={b['order']:b for b in view['bodies']}
    occupied=list(meta['label_boxes'])
    protected=[]
    for p in people:
        for k,s in zip(p['joints'],p['joint_states']):
            if s=='HIGH':
                x,y=pt(k);protected.append((x-12,y-12,x+12,y+12))
    badges=[];extra_labels=[]
    def line(points,c,width):d.line([pt(p) for p in points],fill=(*c,230),width=width)
    def circle(point,r,c,width=2):
        x,y=pt(point);d.ellipse((x-r,y-r,x+r,y+r),outline=(*c,255),width=width)
    def skeleton(order,width):
        p=by_order.get(order)
        if not p:return
        c=tuple(p['identity_color'])
        for (a,b),state in zip(EDGES,p['edge_states']):
            if state:line([p['joints'][a],p['joints'][b]],c,width)
        for k,state in zip(p['joints'],p['joint_states']):
            if state=='HIGH':circle(k,5,c,2)
    def box(order,c,width):
        p=by_order.get(order)
        if p:
            a,b=pt(p['bbox'][:2]),pt(p['bbox'][2:])
            d.rectangle((*a,*b),outline=(*c,255),width=width)
    def badge(label,position,c):badges.append((label,pt(position),c))
    visible_pairs=[]
    if kind=='groups':
        for i,g in enumerate(view['groups']):
            c=color(i+1);poly=[pt(p) for p in g['polygon']]
            d.line(poly+[poly[0]],fill=(*c,255),width=3)
            for a,b in g['edges']:line([bs[a]['center'],bs[b]['center']],c,2)
            center=[sum(bs[o]['center'][j] for o in g['orders'])/len(g['orders']) for j in (0,1)]
            badge(g['group']+' / '+str(len(g['orders']))+'명',center,c)
    elif kind=='motion':
        support={o for p in view['pairs'] if p['highlight'] for o in p['orders']}
        for o in support:skeleton(o,4)
        joint_levels={};torsos={}
        for p in view['pairs']:
            if not p['level'] and not p['highlight']:continue
            visible_pairs.append(p['token']);c=color(int(p['token'][1:]))
            centers=[b['center'] for b in p['bodies']]
            line(centers,c,2 if p['level']<3 else 4)
            badge(p['token'],[(centers[0][j]+centers[1][j])/2 for j in (0,1)],c)
            for e in p['evidence']:
                if not e['level']:continue
                order=p['orders'][e['actor']]
                side=e['side'];sh,wr=(5,9) if side=='L' else (6,10)
                for joint in (sh,wr):joint_levels[(order,joint)]=max(joint_levels.get((order,joint),0),e['level'])
                for o in p['orders']:torsos[o]=max(torsos.get(o,0),e['level'])
        for order,level in torsos.items():
            b=bs[order];p=by_order[order];k=p['joints'];source=b['torso_source']
            if source=='mid':a=[(k[5][j]+k[6][j])/2 for j in (0,1)];z=[(k[11][j]+k[12][j])/2 for j in (0,1)]
            else:a,z=(k[5],k[11]) if source=='left' else (k[6],k[12])
            line([a,z],(235,235,235),level+1);circle(b['center'],5+level,(235,235,235),2)
        for (order,j),level in joint_levels.items():
            circle(by_order[order]['joints'][j],(6,10,15)[level-1],(255,218,115) if level==1 else (255,255,255),level+1)
    else:
        assert kind=='association'
        for p in view['pairs']:
            a=p['association']
            if not a['difference']:continue
            visible_pairs.append(p['token']);c=color(int(p['token'][1:]))
            centers=[b['center'] for b in p['bodies']]
            line(centers,c,3);badge(p['token'],[(centers[0][j]+centers[1][j])/2 for j in (0,1)],c)
            affected=set(affected_orders(a))
            for slot,order in enumerate(p['orders']):
                if order not in affected:continue
                skeleton(order,4);box(order,(255,95,205),4)
                if a['previous_bodies']:
                    old=a['previous_bodies'][slot]['center'];new=p['bodies'][slot]['center']
                    if a['previous_token']==a['token']:
                        line([old,new],c,3)
                    circle(old,4,c,2);circle(new,7,c,2)
    # Preserve the original ID labels above all diagnostic strokes.
    for rect in occupied:d.rectangle(rect,fill=(0,0,0,0))
    panel=Image.alpha_composite(panel.convert('RGBA'),marks).convert('RGB')
    draw=ImageDraw.Draw(panel)
    for label,position,c in badges:
        bb=draw.textbbox((0,0),label,font=FONT[22]);w,h=bb[2]-bb[0]+12,bb[3]-bb[1]+10
        x,y=position
        near=[(x+10,y+10),(x-w-10,y+10),(x+10,y-h-10),(x-w-10,y-h-10)]
        grid=[(xx,yy) for yy in range(6,IH-h,36) for xx in range(6,IW-w,max(60,w))]
        grid.sort(key=lambda z:(z[0]-x)**2+(z[1]-y)**2)
        found=None
        for xx,yy in near+grid:
            r=(xx,yy,xx+w,yy+h)
            if xx<0 or yy<0 or r[2]>=IW or r[3]>=IH:continue
            if any(intersects(r,z) for z in occupied+protected):continue
            found=r;break
        assert found is not None,('PAIR_BADGE_NO_SPACE',label)
        occupied.append(found);extra_labels.append(found)
        draw.rectangle(found,fill='#101820',outline=c,width=2)
        draw.text((found[0]+6-bb[0],found[1]+5-bb[1]),label,font=FONT[22],fill=c)
    assert len(visible_pairs)==len(set(visible_pairs))
    return panel,dict(label_count=len(occupied),label_overlap=0,extra_labels=len(extra_labels),
        group_overlap_pixels=overlap,visible_pairs=visible_pairs,drawn_people=[p['raw_order'] for p in people],
        shown_joints=sum(s=='HIGH' for p in people for s in p['joint_states']))
