#!/usr/bin/env python3
"""OKELIS cover artwork. Three alternatives to the façade grid.
Each is drawn from a real analysis form rather than decoration."""
import math, os, re

OUT = os.path.join(os.path.dirname(__file__), "..", "assets", "gfx")
NAVY, BLUE, BLUE2, BLUE3 = "#021849", "#1B5AA8", "#3D79C4", "#7BA3D6"
SKY, RED, REDLT = "#B9CDE8", "#E1330F", "#FE7C60"


def write(name, body, w, h):
    body = re.sub(r"&(?!(amp|lt|gt|quot|apos|#)\w*;)", "&amp;", body)
    with open(os.path.join(OUT, name), "w") as f:
        f.write(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {h}" '
                f'width="{w}" height="{h}" fill="none">{body}</svg>')
    print("·", name)


# ------------------------------------------------------------------
# A. AXONOMETRIC MASSING — stacked floor plates, performance-tinted
# ------------------------------------------------------------------
def axon():
    W, H = 900, 1180
    g = []
    cx, base = 430, 940
    # isometric basis
    ax, ay = 0.866, 0.50
    def P(x, y, z):
        return (cx + (x - y) * ax * 1.0, base - (x + y) * ay * 0.58 - z)

    def plate(size, z, stroke, fill=None, op=1.0, sw=1.1):
        q = [P(-size, -size, z), P(size, -size, z), P(size, size, z), P(-size, size, z)]
        d = "M" + "L".join(f"{x:.1f} {y:.1f}" for x, y in q) + "Z"
        s = ""
        if fill:
            s += f'<path d="{d}" fill="{fill}" opacity="{op}"/>'
        s += f'<path d="{d}" stroke="{stroke}" stroke-width="{sw}" opacity="{op}"/>'
        return s

    FL, SZ, GAP = 34, 250, 19
    # floor plates, tinted by a modelled performance gradient
    ramp = [SKY, BLUE3, BLUE2, BLUE, NAVY]
    for i in range(FL + 1):
        z = i * GAP
        t = i / FL
        # a few plates carry an accent, as if flagged in the analysis
        acc = (i == 14)
        col = BLUE2 if acc else ramp[min(4, int((1 - t) * 4.9))]
        op = .95 if acc else (.30 + .5 * (1 - t))
        g.append(plate(SZ, z, BLUE3 if not acc else RED,
                       fill=col if (i % 4 == 0 or acc) else None,
                       op=(op * .55) if not acc else .55, sw=1.6 if acc else .9))
    # vertical mullions, drawn over the plates so the massing reads
    for sx, sy in [(-1, -1), (1, -1), (1, 1), (-1, 1)]:
        a = P(sx * SZ, sy * SZ, 0); b = P(sx * SZ, sy * SZ, FL * GAP)
        g.append(f'<path d="M{a[0]:.1f} {a[1]:.1f}L{b[0]:.1f} {b[1]:.1f}" '
                 f'stroke="{BLUE3}" stroke-width="1.5" opacity=".85"/>')
    # ground datum and shadow
    gq = [P(-SZ * 1.9, -SZ * 1.9, 0), P(SZ * 1.9, -SZ * 1.9, 0),
          P(SZ * 1.9, SZ * 1.9, 0), P(-SZ * 1.9, SZ * 1.9, 0)]
    gd = "M" + "L".join(f"{x:.1f} {y:.1f}" for x, y in gq) + "Z"
    g.insert(0, f'<path d="{gd}" stroke="{BLUE}" stroke-width=".7" opacity=".22"/>')
    # solar rays
    for i in range(6):
        x0 = 40 + i * 150
        g.append(f'<path d="M{x0} 40L{x0 - 210} {H - 40}" stroke="{REDLT}" '
                 f'stroke-width=".8" opacity="{.16 - i * .017:.3f}"/>')
    write("cover-axon.svg", "".join(g), W, H)


# ------------------------------------------------------------------
# B. RIDGELINE FIELD — annual hourly irradiance, one line per day
# ------------------------------------------------------------------
def ridge():
    W, H = 900, 1180
    g = []
    rows, pts = 74, 190
    x0, x1 = 40, 860
    top, bot = 210, 1080
    step = (bot - top) / rows
    for r in range(rows):
        day = r / (rows - 1)
        yb = top + r * step
        # seasonal envelope: longer, stronger days mid-year
        season = math.sin(math.pi * day)
        amp = 46 + 74 * season
        width = .30 + .22 * season
        path = []
        for i in range(pts + 1):
            u = i / pts
            hour = (u - .5) / width
            v = math.exp(-hour * hour * 3.1)
            v *= 1 + .10 * math.sin(u * 27 + r * .8) * season   # cloud texture
            y = yb - amp * max(0.0, v)
            path.append(f"{x0 + u * (x1 - x0):.1f} {y:.1f}")
        d = "M" + "L".join(path)
        # opaque fill so nearer rows occlude the ones behind, as in a real ridgeline
        g.append(f'<path d="{d}L{x1} {yb + 3:.1f}L{x0} {yb + 3:.1f}Z" fill="{NAVY}"/>')
        peak = season
        col = RED if peak > .93 else (BLUE2 if peak > .55 else BLUE)
        g.append(f'<path d="{d}" stroke="{col}" stroke-width="1.25" '
                 f'opacity="{.30 + .62 * season:.2f}"/>')
    write("cover-ridge.svg", "".join(g), W, H)


# ------------------------------------------------------------------
# C. TECHNICAL SECTION — façade, shading, sun angles, dimensions
# ------------------------------------------------------------------
def section(light=True):
    W, H = 900, 1180
    g = []
    LN = NAVY if light else BLUE3
    LN2 = BLUE if light else BLUE2
    L, Rt = 70, 720          # core side, façade line
    top, bot = 150, 1090
    fl = 8
    fh = (bot - top) / fl
    # slabs
    for i in range(fl + 1):
        y = top + i * fh
        g.append(f'<path d="M{L} {y:.0f}H{Rt}" stroke="{LN}" stroke-width="2.2"/>')
        g.append(f'<path d="M{L} {y + 9:.0f}H{Rt}" stroke="{LN}" stroke-width=".7" opacity=".35"/>')
    # façade line and core
    g.append(f'<path d="M{Rt} {top}V{bot}" stroke="{LN}" stroke-width="2.2"/>')
    g.append(f'<path d="M{L} {top}V{bot}" stroke="{LN}" stroke-width="1" '
             f'opacity=".4" stroke-dasharray="7 5"/>')
    # glazing + external shading fin per floor, with daylight wedge
    for i in range(fl):
        y = top + i * fh
        gy0, gy1 = y + 24, y + fh - 16
        g.append(f'<path d="M{Rt - 8} {gy0:.0f}V{gy1:.0f}" stroke="{LN2}" stroke-width="5"/>')
        g.append(f'<path d="M{Rt} {gy0:.0f}L{Rt + 62} {gy0 - 20:.0f}" stroke="{LN}" stroke-width="3"/>')
        g.append(f'<path d="M{Rt - 8} {gy0 + 6:.0f}L{L + 40} {gy1:.0f}L{Rt - 8} {gy1:.0f}Z" '
                 f'fill="{RED}" opacity=".16"/>')
        for k in range(4):
            t = k / 3
            g.append(f'<path d="M{Rt - 8} {gy0 + 8:.0f}L{L + 40 + t * 300:.0f} {gy1:.0f}" '
                     f'stroke="{RED}" stroke-width="1" opacity="{.40 - t * .22:.2f}"/>')
    # sun angles
    for ang, op in ((24, .5), (48, .34), (72, .2)):
        a = math.radians(ang)
        x2 = Rt + 150 * math.cos(a); y2 = top - 60 - 150 * math.sin(a)
        g.append(f'<path d="M{Rt} {top - 60}L{x2:.0f} {y2:.0f}" stroke="{RED}" '
                 f'stroke-width="1.1" opacity="{op}"/>')
        g.append(f'<text x="{x2 + 8:.0f}" y="{y2 + 4:.0f}" fill="{RED}" opacity="{op + .2:.2f}" '
                 f'font-family="SF Pro Text" font-size="15" font-weight="600">{ang}°</text>')
    write("cover-section.svg", "".join(g), W, H)


if __name__ == "__main__":
    axon(); ridge(); section()
