# -*- coding: utf-8 -*-
"""
경량 SVG(stroke) -> PNG 래스터라이저 (cairo 불필요, PIL만 사용)
- assets/icons/*.svg (viewBox 0 0 24 24, fill none, stroke currentColor, stroke-width 1.75, round cap/join)
- 지원: <path d> M/m L/l H/h V/v C/c S/s A/a Z/z, <circle>, <rect rx transform=rotate>
- 렌더: 고배율 supersample 후 LANCZOS 다운스케일, 라운드 캡/조인 = 두꺼운 라인 + 정점 원
- 색: stroke=맥락색(RGB). fill 없음(선 아이콘).
재사용: from icon_raster import render_icon; render_icon('ai-sparkle', (r,g,b), out_png, px=192)
"""
import os, re, math
from PIL import Image, ImageDraw

ICON_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "icons")

_num = re.compile(r'[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?')

def _nums(s):
    return [float(x) for x in _num.findall(s)]

# ---- cubic / arc flattening ----
def _cubic(p0, p1, p2, p3, n=18):
    pts = []
    for i in range(1, n+1):
        t = i/n; mt = 1-t
        x = mt**3*p0[0] + 3*mt*mt*t*p1[0] + 3*mt*t*t*p2[0] + t**3*p3[0]
        y = mt**3*p0[1] + 3*mt*mt*t*p1[1] + 3*mt*t*t*p2[1] + t**3*p3[1]
        pts.append((x, y))
    return pts

def _arc(p0, rx, ry, phi, large, sweep, p1, n=24):
    # endpoint -> center parameterization (SVG spec)
    if rx == 0 or ry == 0:
        return [p1]
    phi = math.radians(phi)
    cosp, sinp = math.cos(phi), math.sin(phi)
    dx = (p0[0]-p1[0])/2.0; dy = (p0[1]-p1[1])/2.0
    x1p = cosp*dx + sinp*dy; y1p = -sinp*dx + cosp*dy
    rx, ry = abs(rx), abs(ry)
    lam = x1p*x1p/(rx*rx) + y1p*y1p/(ry*ry)
    if lam > 1:
        s = math.sqrt(lam); rx *= s; ry *= s
    num = rx*rx*ry*ry - rx*rx*y1p*y1p - ry*ry*x1p*x1p
    den = rx*rx*y1p*y1p + ry*ry*x1p*x1p
    co = math.sqrt(max(0.0, num/den)) if den else 0.0
    if large == sweep:
        co = -co
    cxp = co*rx*y1p/ry; cyp = -co*ry*x1p/rx
    cx = cosp*cxp - sinp*cyp + (p0[0]+p1[0])/2.0
    cy = sinp*cxp + cosp*cyp + (p0[1]+p1[1])/2.0
    def ang(ux, uy, vx, vy):
        d = math.sqrt((ux*ux+uy*uy)*(vx*vx+vy*vy))
        c = max(-1.0, min(1.0, (ux*vx+uy*vy)/d)) if d else 1.0
        a = math.acos(c)
        if ux*vy - uy*vx < 0: a = -a
        return a
    th1 = ang(1, 0, (x1p-cxp)/rx, (y1p-cyp)/ry)
    dth = ang((x1p-cxp)/rx, (y1p-cyp)/ry, (-x1p-cxp)/rx, (-y1p-cyp)/ry)
    if not sweep and dth > 0: dth -= 2*math.pi
    if sweep and dth < 0: dth += 2*math.pi
    pts = []
    for i in range(1, n+1):
        th = th1 + dth*i/n
        x = cosp*rx*math.cos(th) - sinp*ry*math.sin(th) + cx
        y = sinp*rx*math.cos(th) + cosp*ry*math.sin(th) + cy
        pts.append((x, y))
    return pts

# flag-aware arc arg regex: rx ry rot large sweep(single 0/1) x y  (repeatable)
_NN = r'[-+]?(?:\d*\.\d+|\d+\.?)'
_ARC = re.compile(
    r'(' + _NN + r')[,\s]*(' + _NN + r')[,\s]*(' + _NN + r')[,\s]*'
    r'([01])[,\s]*([01])[,\s]*(' + _NN + r')[,\s]*(' + _NN + r')')

def _parse_path(d):
    """반환: list of subpaths, each subpath = list of (x,y) polyline points."""
    chunks = re.findall(r'([MmLlHhVvCcSsAaZz])([^MmLlHhVvCcSsAaZz]*)', d)
    subs = []; cur = []
    x = y = 0.0; sx = sy = 0.0
    prev_ctrl = None; prev_cmd = None
    for cmd, arg in chunks:
        rel = cmd.islower(); c = cmd.upper()
        if c == 'A':
            for m in _ARC.finditer(arg):
                rx, ry, rot, large, sweep, nx, ny = (float(m.group(k)) for k in range(1, 8))
                if rel: nx += x; ny += y
                cur += _arc((x, y), rx, ry, rot, int(large), int(sweep), (nx, ny))
                x, y = nx, ny
            prev_cmd = c; prev_ctrl = None; continue
        vals = _nums(arg)
        if c == 'Z':
            cur.append((sx, sy)); x, y = sx, sy
            subs.append(cur); cur = []
            prev_cmd = c; prev_ctrl = None; continue
        j = 0
        first_pair = True
        while j < len(vals) or (c in ('M', 'L', 'C', 'S', 'H', 'V') and False):
            if c == 'M':
                nx, ny = vals[j], vals[j+1]; j += 2
                if rel: nx += x; ny += y
                if first_pair:
                    if cur: subs.append(cur)
                    cur = [(nx, ny)]; sx, sy = nx, ny
                else:
                    cur.append((nx, ny))
                x, y = nx, ny; first_pair = False
            elif c == 'L':
                nx, ny = vals[j], vals[j+1]; j += 2
                if rel: nx += x; ny += y
                cur.append((nx, ny)); x, y = nx, ny
            elif c == 'H':
                nx = vals[j]; j += 1
                if rel: nx += x
                cur.append((nx, y)); x = nx
            elif c == 'V':
                ny = vals[j]; j += 1
                if rel: ny += y
                cur.append((x, ny)); y = ny
            elif c == 'C':
                x1, y1, x2, y2, nx, ny = vals[j:j+6]; j += 6
                if rel: x1+=x; y1+=y; x2+=x; y2+=y; nx+=x; ny+=y
                cur += _cubic((x, y), (x1, y1), (x2, y2), (nx, ny))
                prev_ctrl = (x2, y2); x, y = nx, ny
            elif c == 'S':
                x2, y2, nx, ny = vals[j:j+4]; j += 4
                if rel: x2+=x; y2+=y; nx+=x; ny+=y
                if prev_cmd in ('C', 'S') and prev_ctrl:
                    x1 = 2*x - prev_ctrl[0]; y1 = 2*y - prev_ctrl[1]
                else:
                    x1, y1 = x, y
                cur += _cubic((x, y), (x1, y1), (x2, y2), (nx, ny))
                prev_ctrl = (x2, y2); x, y = nx, ny
            else:
                break
            if j >= len(vals):
                break
        prev_cmd = c
        if c not in ('C', 'S'):
            prev_ctrl = None
    if cur: subs.append(cur)
    return subs

def _circle_pts(cx, cy, r, n=48):
    return [(cx + r*math.cos(2*math.pi*i/n), cy + r*math.sin(2*math.pi*i/n)) for i in range(n+1)]

def _rect_pts(x, y, w, h, rx=0, transform=None, n=6):
    rx = min(rx, w/2, h/2) if rx else 0
    pts = []
    if rx <= 0:
        pts = [(x, y), (x+w, y), (x+w, y+h), (x, y+h), (x, y)]
    else:
        def arc(cx, cy, a0, a1):
            return [(cx+rx*math.cos(math.radians(a)), cy+rx*math.sin(math.radians(a)))
                    for a in [a0 + (a1-a0)*k/n for k in range(n+1)]]
        pts += [(x+rx, y)]
        pts += [(x+w-rx, y)]
        pts += arc(x+w-rx, y+rx, -90, 0)
        pts += [(x+w, y+h-rx)]
        pts += arc(x+w-rx, y+h-rx, 0, 90)
        pts += [(x+rx, y+h)]
        pts += arc(x+rx, y+h-rx, 90, 180)
        pts += [(x, y+rx)]
        pts += arc(x+rx, y+rx, 180, 270)
    if transform:
        m = re.match(r'rotate\(\s*([-\d.]+)\s+([-\d.]+)\s+([-\d.]+)\s*\)', transform)
        if m:
            ang = math.radians(float(m.group(1))); ox = float(m.group(2)); oy = float(m.group(3))
            ca, sa = math.cos(ang), math.sin(ang)
            pts = [((px-ox)*ca-(py-oy)*sa+ox, (px-ox)*sa+(py-oy)*ca+oy) for px, py in pts]
    return pts

def _extract(svg_text):
    """SVG -> list of polylines (in 24-unit viewBox coords)."""
    polys = []
    for m in re.finditer(r'<path[^>]*\bd="([^"]+)"', svg_text):
        polys += _parse_path(m.group(1))
    for m in re.finditer(r'<circle[^>]*>', svg_text):
        a = m.group(0)
        cx = re.search(r'cx="([-\d.]+)"', a); cy = re.search(r'cy="([-\d.]+)"', a); r = re.search(r'r="([-\d.]+)"', a)
        if cx and cy and r:
            polys.append(_circle_pts(float(cx.group(1)), float(cy.group(1)), float(r.group(1))))
    for m in re.finditer(r'<rect[^>]*>', svg_text):
        a = m.group(0)
        def g(k, d=0.0):
            mm = re.search(k+r'="([-\d.]+)"', a); return float(mm.group(1)) if mm else d
        tr = re.search(r'transform="([^"]+)"', a)
        polys.append(_rect_pts(g('x'), g('y'), g('width'), g('height'), g('rx', 0),
                               tr.group(1) if tr else None))
    return polys

_cache = {}
def render_icon(name, rgb, out_png, px=192, stroke_w=1.75, pad=1.0):
    """name: 파일명(확장자 무관). rgb: (r,g,b) 0-255. out_png 저장. 이미 있으면 재사용."""
    key = (name, tuple(rgb), px)
    if key in _cache and os.path.exists(out_png):
        return out_png
    path = os.path.join(ICON_DIR, name if name.endswith('.svg') else name+'.svg')
    with open(path, 'r', encoding='utf-8') as f:
        svg = f.read()
    polys = _extract(svg)
    SS = 4  # supersample
    W = px*SS
    scale = W/24.0
    img = Image.new('RGBA', (W, W), (0, 0, 0, 0))
    dr = ImageDraw.Draw(img)
    col = (int(rgb[0]), int(rgb[1]), int(rgb[2]), 255)
    lw = max(1, int(round(stroke_w*scale)))
    rcap = lw/2.0
    for poly in polys:
        if len(poly) < 2:
            continue
        sp = [(px_*scale, py_*scale) for px_, py_ in poly]
        dr.line(sp, fill=col, width=lw, joint='curve')
        # round caps/joins via dots at each vertex
        for (vx, vy) in sp:
            dr.ellipse([vx-rcap, vy-rcap, vx+rcap, vy+rcap], fill=col)
    img = img.resize((px, px), Image.LANCZOS)
    img.save(out_png)
    _cache[key] = out_png
    return out_png

if __name__ == "__main__":
    # 테스트: 20종 x 1색 몽타주
    outdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "_iconpng")
    os.makedirs(outdir, exist_ok=True)
    names = sorted(f[:-4] for f in os.listdir(ICON_DIR) if f.endswith('.svg'))
    cols = 5
    cell = 96
    mont = Image.new('RGBA', (cols*cell, ((len(names)+cols-1)//cols)*cell), (255, 255, 255, 255))
    from PIL import ImageDraw as _ID
    md = _ID.Draw(mont)
    for i, nm in enumerate(names):
        outp = os.path.join(outdir, nm+".png")
        render_icon(nm, (0x00, 0x66, 0xB3), outp, px=72)
        ic = Image.open(outp)
        cx = (i % cols)*cell + 12; cy = (i//cols)*cell + 6
        mont.alpha_composite(ic, (cx, cy))
        md.text((cx, cy+74), nm[:14], fill=(60, 60, 60))
    mont.convert('RGB').save(os.path.join(outdir, "_montage.png"))
    print("OK", len(names), "icons ->", os.path.join(outdir, "_montage.png"))
