"""
Top-down icons for VTT Map props (3D assets): 128x128 transparent PNGs, drawn at 4x and scaled down.

    python tools/make_props.py props

Used by the Add drawer, the 2D view and the forum PNG; the 3D view builds matching models
(propModel() in assets/vtt3d.js). Not deployed: only its output (props/*.png) is.
"""
import math
import os
import sys

from PIL import Image, ImageDraw, ImageFilter

S = 512  # work size (downscaled to 128)
C = S / 2


def new():
    return Image.new('RGBA', (S, S), (0, 0, 0, 0))


def shadow(img, r=0.36, off=(16, 22), blur=22, alpha=110):
    """Soft drop shadow under whatever is drawn (call last)."""
    a = img.split()[3]
    sh = Image.new('RGBA', (S, S), (0, 0, 0, 0))
    sh.putalpha(a.point(lambda v: min(alpha, v)))
    sh = sh.filter(ImageFilter.GaussianBlur(blur))
    base = Image.new('RGBA', (S, S), (0, 0, 0, 0))
    base.alpha_composite(sh, off)
    base.alpha_composite(img)
    return base


def circle(d, cx, cy, r, fill, outline=None, width=0):
    d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=fill, outline=outline, width=width)


def poly_star(cx, cy, r1, r2, n, rot=0):
    pts = []
    for i in range(n * 2):
        r = r1 if i % 2 == 0 else r2
        a = rot + i * math.pi / n
        pts.append((cx + r * math.cos(a), cy + r * math.sin(a)))
    return pts


def flame(d, cx, cy, r):
    d.polygon(poly_star(cx, cy, r, r * 0.55, 7, 0.3), fill=(255, 120, 30, 255))
    d.polygon(poly_star(cx, cy, r * 0.7, r * 0.38, 6, 0.8), fill=(255, 190, 60, 255))
    circle(d, cx, cy, r * 0.3, (255, 245, 190, 255))


# ---------------------------------------------------------------------------------------------
def p_spikes():
    img = new()
    d = ImageDraw.Draw(img)
    d.rounded_rectangle([60, 60, 452, 452], 40, fill=(70, 52, 36, 255), outline=(40, 28, 18, 255), width=10)
    for gy in range(3):
        for gx in range(3):
            cx, cy = 136 + gx * 120, 136 + gy * 120
            d.polygon([(cx, cy - 46), (cx + 40, cy + 26), (cx - 40, cy + 26)], fill=(150, 156, 164, 255))
            d.polygon([(cx, cy - 46), (cx + 40, cy + 26), (cx, cy + 6)], fill=(95, 100, 108, 255))
            circle(d, cx, cy - 6, 6, (230, 235, 240, 255))
    return shadow(img)


def p_campfire():
    img = new()
    d = ImageDraw.Draw(img)
    for i in range(9):
        a = i / 9 * 2 * math.pi
        circle(d, C + math.cos(a) * 170, C + math.sin(a) * 170, 44, (120, 116, 110, 255), (70, 66, 62, 255), 6)
    for ang in (35, -35, 90):
        a = math.radians(ang)
        dx, dy = math.cos(a) * 140, math.sin(a) * 140
        d.line([(C - dx, C - dy), (C + dx, C + dy)], fill=(96, 62, 34, 255), width=46)
        d.line([(C - dx, C - dy), (C + dx, C + dy)], fill=(130, 86, 48, 255), width=22)
    flame(d, C, C, 110)
    return shadow(img)


def p_brazier():
    img = new()
    d = ImageDraw.Draw(img)
    for i in range(3):
        a = i / 3 * 2 * math.pi + 0.5
        d.line([(C, C), (C + math.cos(a) * 210, C + math.sin(a) * 210)], fill=(50, 50, 56, 255), width=26)
    circle(d, C, C, 165, (60, 62, 70, 255), (30, 30, 34, 255), 12)
    circle(d, C, C, 128, (30, 22, 18, 255))
    flame(d, C, C, 115)
    return shadow(img)


def p_statue():
    img = new()
    d = ImageDraw.Draw(img)
    d.rectangle([90, 90, 422, 422], fill=(150, 146, 136, 255), outline=(98, 94, 86, 255), width=14)
    d.rectangle([130, 130, 382, 382], outline=(118, 114, 106, 255), width=6)
    d.ellipse([150, 190, 362, 330], fill=(186, 182, 172, 255), outline=(120, 116, 108, 255), width=8)  # shoulders
    circle(d, C, 236, 62, (205, 201, 190, 255), (120, 116, 108, 255), 8)  # head
    d.line([(330, 270), (420, 150)], fill=(168, 172, 180, 255), width=16)  # sword
    return shadow(img)


def p_pillar():
    img = new()
    d = ImageDraw.Draw(img)
    d.rectangle([96, 96, 416, 416], fill=(132, 128, 120, 255), outline=(90, 86, 80, 255), width=12)
    circle(d, C, C, 128, (178, 174, 164, 255), (110, 106, 98, 255), 10)
    for i in range(16):
        a = i / 16 * 2 * math.pi
        d.line([(C + math.cos(a) * 60, C + math.sin(a) * 60), (C + math.cos(a) * 120, C + math.sin(a) * 120)], fill=(140, 136, 128, 255), width=6)
    circle(d, C, C, 56, (196, 192, 182, 255))
    return shadow(img)


def p_altar():
    img = new()
    d = ImageDraw.Draw(img)
    d.rectangle([70, 150, 442, 362], fill=(122, 112, 92, 255), outline=(78, 70, 56, 255), width=14)
    d.rectangle([100, 178, 412, 334], outline=(150, 140, 116, 255), width=6)
    d.polygon([(C, 200), (C + 30, 256), (C, 312), (C - 30, 256)], outline=(170, 40, 40, 255), width=8)
    for cx in (130, 382):
        circle(d, cx, 256, 20, (236, 226, 200, 255))
        circle(d, cx, 256, 9, (255, 190, 60, 255))
    return shadow(img)


def p_stump():
    img = new()
    d = ImageDraw.Draw(img)
    for a in (0.3, 1.9, 3.6, 5.0):
        d.line([(C, C), (C + math.cos(a) * 220, C + math.sin(a) * 220)], fill=(84, 56, 34, 255), width=40)
    circle(d, C, C, 160, (92, 62, 38, 255))
    circle(d, C, C, 130, (196, 156, 102, 255))
    for r in range(110, 0, -22):
        circle(d, C + 6, C - 4, r, None, (160, 118, 70, 255), 5)
    d.line([(C, C), (C + 120, C - 30)], fill=(120, 80, 46, 255), width=6)
    return shadow(img)


def p_tree():
    img = new()
    d = ImageDraw.Draw(img)
    # kept inside the icon with room for its soft shadow (no square edge when it's turned or scaled up)
    k = 0.8
    for dx, dy, r, c in [(0, 0, 230, (34, 82, 32)), (-70, -50, 140, (46, 104, 40)), (80, -30, 120, (52, 116, 44)),
                         (-20, 80, 130, (44, 98, 38)), (-40, -80, 70, (78, 146, 62)), (60, 30, 60, (70, 136, 56))]:
        circle(d, C + dx * k, C + dy * k, r * k, c + (255,))
    return shadow(img, off=(16, 22), blur=22, alpha=125)


def p_bush():
    img = new()
    d = ImageDraw.Draw(img)
    for cx, cy, r, c in [(C - 70, C + 20, 120, (38, 90, 34)), (C + 80, C + 30, 110, (42, 96, 36)), (C, C - 70, 120, (46, 104, 40)),
                         (C + 10, C + 60, 100, (50, 112, 42)), (C - 60, C - 60, 50, (84, 150, 64)), (C + 60, C - 20, 40, (90, 160, 70))]:
        circle(d, cx, cy, r, c + (255,))
    return shadow(img)


def p_rock():
    img = new()
    d = ImageDraw.Draw(img)
    pts = [(120, 200), (200, 110), (330, 120), (420, 230), (390, 360), (270, 420), (150, 380), (100, 290)]
    d.polygon(pts, fill=(122, 118, 112, 255), outline=(70, 66, 62, 255))
    d.polygon([(200, 110), (330, 120), (300, 230), (190, 220)], fill=(160, 156, 148, 255))
    d.polygon([(300, 230), (420, 230), (390, 360), (300, 330)], fill=(98, 94, 90, 255))
    d.line(pts + [pts[0]], fill=(70, 66, 62, 255), width=10)
    return shadow(img)


def p_barrel():
    img = new()
    d = ImageDraw.Draw(img)
    circle(d, C, C, 170, (110, 72, 40, 255), (60, 40, 22, 255), 10)
    for i in range(-3, 4):
        d.line([(C + i * 44, C - 160), (C + i * 44, C + 160)], fill=(84, 54, 30, 255), width=5)
    circle(d, C, C, 170, None, (90, 94, 100, 255), 18)
    circle(d, C, C, 120, None, (90, 94, 100, 255), 10)
    circle(d, C + 40, C - 40, 16, (70, 46, 26, 255))
    return shadow(img)


def p_crate():
    img = new()
    d = ImageDraw.Draw(img)
    d.rectangle([96, 96, 416, 416], fill=(176, 128, 74, 255), outline=(98, 66, 36, 255), width=18)
    for y in (176, 256, 336):
        d.line([(110, y), (402, y)], fill=(128, 88, 48, 255), width=6)
    d.line([(110, 110), (402, 402)], fill=(140, 96, 52, 255), width=26)
    d.rectangle([96, 96, 416, 416], outline=(98, 66, 36, 255), width=18)
    return shadow(img)


def p_chest():
    img = new()
    d = ImageDraw.Draw(img)
    d.rounded_rectangle([86, 150, 426, 362], 26, fill=(128, 78, 40, 255), outline=(70, 42, 20, 255), width=12)
    for x in (150, 362):
        d.rectangle([x - 16, 150, x + 16, 362], fill=(150, 150, 158, 255))
    d.line([(86, 256), (426, 256)], fill=(80, 48, 24, 255), width=8)
    d.rectangle([C - 26, 236, C + 26, 290], fill=(220, 180, 70, 255), outline=(120, 90, 30, 255), width=6)
    return shadow(img)


def p_table():
    img = new()
    d = ImageDraw.Draw(img)
    d.rectangle([60, 130, 452, 382], fill=(150, 104, 62, 255), outline=(90, 58, 30, 255), width=14)
    for y in (192, 256, 320):
        d.line([(70, y), (442, y)], fill=(120, 80, 44, 255), width=5)
    circle(d, 180, 220, 30, (210, 210, 200, 255), (120, 120, 110, 255), 6)  # plate
    circle(d, 330, 290, 22, (120, 70, 40, 255), (60, 30, 14, 255), 6)   # mug
    return shadow(img)


def p_bones():
    img = new()
    d = ImageDraw.Draw(img)
    for a in (35, -35):
        r = math.radians(a)
        dx, dy = math.cos(r) * 150, math.sin(r) * 150
        d.line([(C - dx, C + 60 - dy), (C + dx, C + 60 + dy)], fill=(226, 218, 196, 255), width=26)
        for sx, sy in ((C - dx, C + 60 - dy), (C + dx, C + 60 + dy)):
            circle(d, sx, sy, 22, (232, 224, 204, 255))
    circle(d, C, C - 40, 95, (238, 232, 214, 255), (150, 140, 120, 255), 8)  # skull
    d.rectangle([C - 50, C + 20, C + 50, C + 70], fill=(238, 232, 214, 255))
    circle(d, C - 34, C - 40, 24, (40, 30, 24, 255))
    circle(d, C + 34, C - 40, 24, (40, 30, 24, 255))
    d.polygon([(C, C - 6), (C - 12, C + 16), (C + 12, C + 16)], fill=(40, 30, 24, 255))
    return shadow(img)


def p_well():
    img = new()
    d = ImageDraw.Draw(img)
    circle(d, C, C, 190, (128, 124, 116, 255), (80, 76, 70, 255), 12)
    for i in range(12):
        a = i / 12 * 2 * math.pi
        d.line([(C + math.cos(a) * 128, C + math.sin(a) * 128), (C + math.cos(a) * 190, C + math.sin(a) * 190)], fill=(86, 82, 76, 255), width=6)
    circle(d, C, C, 128, (18, 34, 54, 255))
    circle(d, C - 30, C - 30, 40, (40, 70, 100, 255))
    d.line([(40, C), (472, C)], fill=(96, 62, 34, 255), width=30)  # beam
    circle(d, C, C, 20, (150, 120, 80, 255))  # bucket rope
    return shadow(img)


PROPS = {
    'sp': p_spikes, 'cf': p_campfire, 'bz': p_brazier, 'st': p_statue, 'pl': p_pillar, 'al': p_altar,
    'su': p_stump, 'tr': p_tree, 'bu': p_bush, 'rk': p_rock, 'ba': p_barrel, 'cr': p_crate,
    'ch': p_chest, 'ta': p_table, 'bo': p_bones, 'we': p_well,
}

if __name__ == '__main__':
    out = sys.argv[1] if len(sys.argv) > 1 else 'props'
    os.makedirs(out, exist_ok=True)
    sheet = Image.new('RGBA', (len(PROPS) * 136 + 8, 144), (40, 46, 40, 255))
    for i, (k, fn) in enumerate(PROPS.items()):
        img = fn().resize((128, 128), Image.LANCZOS)
        img.save(os.path.join(out, k + '.png'), optimize=True)
        sheet.alpha_composite(img, (8 + i * 136, 8))
    sheet.save(os.path.join(out, 'preview.png'))
    print('ok', len(PROPS))
