#!/usr/bin/env python3
"""
gen_face.py  –  Run ONCE to generate assets/face_wire.png
─────────────────────────────────────────────────────────
Uses matplotlib's 3-D surface renderer to produce a wireframe face image
that display.py loads.  Run it once; the PNG is then part of your project.

    python gen_face.py

Dependencies (dev machine only – NOT needed on the target device):
    sudo apt install python3-matplotlib   # or: pip install matplotlib
"""

import os
import math
import argparse
import numpy as np
import matplotlib
matplotlib.use('Agg')                       # off-screen — no display needed
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D    # noqa: F401

# ── Face width profile ────────────────────────────────────────────────────────
# (normalised_height, width_fraction)   0 = crown, 1 = chin
# Widest point is at the cheekbones (~t=0.37), not the crown.
# Crown and forehead are slightly narrower; jaw/chin taper below cheeks.
_PROFILE = [
    (0.00, 0.82),   # crown
    (0.10, 0.88),   # upper skull
    (0.22, 0.94),   # temples / forehead
    (0.37, 1.00),   # cheekbones — widest
    (0.50, 0.96),   # nose level
    (0.62, 0.84),   # mouth level
    (0.73, 0.68),   # jaw angle
    (0.83, 0.50),   # lower jaw
    (0.92, 0.30),   # chin
    (1.00, 0.08),   # chin tip
]


def _face_r(v: float) -> float:
    """Radial width at normalised height v ∈ [0, 1]."""
    for i in range(len(_PROFILE) - 1):
        t0, w0 = _PROFILE[i]
        t1, w1 = _PROFILE[i + 1]
        if t0 <= v <= t1:
            return w0 + (v - t0) / (t1 - t0) * (w1 - w0)
    return 0.0


def face_surface(nu: int = 22, nv: int = 28):
    """
    Build the face mesh arrays.
    Axis convention chosen to match matplotlib's default camera behaviour:
      X  = left / right
      Y  = depth  (positive = toward viewer / front of face)
      Z  = up / down  (+1 = crown, −1 = chin)
    To view from the front: view_init(elev, azim=90)  ← camera is at +Y
    """
    u_vals = np.linspace(-math.pi / 2, math.pi / 2, nu)   # front hemisphere
    v_vals = np.linspace(0.0, 1.0, nv)                     # crown → chin

    X = np.zeros((nv, nu))
    Y = np.zeros((nv, nu))
    Z = np.zeros((nv, nu))

    for j, v in enumerate(v_vals):
        r = _face_r(v)
        z = math.cos(math.pi * v)   # +1 at crown, −1 at chin
        for i, u in enumerate(u_vals):
            X[j, i] = r * math.sin(u)    # left/right
            Y[j, i] = r * math.cos(u)    # depth (front = max)
            Z[j, i] = z                   # height

    return X, Y, Z


def generate(out_path: str = 'assets/face_wire.png',
             img_w: int = 400, img_h: int = 500,
             line_col: str = '#606060') -> None:
    dpi = 100
    fig = plt.figure(figsize=(img_w / dpi, img_h / dpi), facecolor='black')
    ax  = fig.add_subplot(111, projection='3d', facecolor='black')

    X, Y, Z = face_surface(nu=18, nv=22)
    ax.plot_wireframe(X, Y, Z,
                      color=line_col,
                      linewidth=0.85,
                      rstride=1, cstride=1)

    # azim=90  →  camera at +Y, looking toward −Y (directly at face)
    # elev=-8  →  camera slightly below horizontal = view from below,
    #             shows chin/jaw, hides flat top of mesh — matches reference
    ax.view_init(elev=-8, azim=90)

    ax.set_axis_off()
    # [x_width, y_depth, z_height]
    # Key: y_depth very small → face is flat/mask-like, not a globe
    ax.set_box_aspect([1.0, 0.28, 1.22])

    plt.subplots_adjust(left=0, right=1, top=1, bottom=0)

    os.makedirs(os.path.dirname(out_path) if os.path.dirname(out_path) else '.',
                exist_ok=True)
    fig.savefig(out_path, dpi=dpi, facecolor='black',
                bbox_inches='tight', pad_inches=0.02)
    plt.close(fig)
    print(f'Saved  {out_path}  ({img_w}×{img_h} px)')


if __name__ == '__main__':
    ap = argparse.ArgumentParser()
    ap.add_argument('--out',    default='assets/face_wire.png')
    ap.add_argument('--width',  type=int, default=400)
    ap.add_argument('--height', type=int, default=500)
    ap.add_argument('--color',  default='#606060')
    args = ap.parse_args()
    generate(args.out, args.width, args.height, args.color)
