"""Circular dial weighing (platform) scale SVG generator.

generate(reading, unit, full_scale, major_step, size) -> str  (self-contained SVG)

FULL-SCALE MAPPING (documented):
  The dial sweeps `full_scale` units over a SWEEP = 270 degrees arc.
  The needle's rest (zero) position is at the LOWER-LEFT, and the scale
  increases clockwise to the LOWER-RIGHT (a 90-deg gap at the bottom).

  Angle convention: clockwise angle measured from 12 o'clock (top), where
    x = cx + r*sin(theta),  y = cy - r*cos(theta).
  Zero sits at -135 deg (lower-left); full scale at +135 deg (lower-right).

    frac         = clamp(reading / full_scale, 0, 1)
    needle_angle = -135 + frac * 270   (degrees, clockwise-from-top)

  Majors are relabelled from 0..full_scale in steps of `major_step`.
  e.g. full_scale=1000, major_step=100 -> 0,100,...,1000 (11 majors).

  Backward compatible: generate(reading, unit) still defaults to a
  0..1000 face with majors every 100.

CIRCLE DIAL MODE (dial="circle"):
  A full 360-deg round face (e.g. a kitchen scale). 0 sits at the top
  (12 o'clock); labels run clockwise every `major_step` all the way
  around. The full-scale value coincides with 0 at the top.

    frac         = reading / full_scale (mod 1 for the needle)
    needle_angle = frac * 360   (degrees, clockwise-from-top)
"""
import math

STYLE = (
    "<style>svg{--crimson:#960000;--gold:#BF9237;--lime:#88C808;"
    "--steel:#4A6488;--cream:#FAF5EE;--ink:#2A2A2A;--border:#D0ACAC;}</style>"
)

SWEEP = 270.0
START_ANGLE = -135.0       # lower-left, clockwise-from-top degrees


def _pt(cx, cy, r, ang_deg):
    a = math.radians(ang_deg)
    return cx + r * math.sin(a), cy - r * math.cos(a)


def _fmt(v):
    if abs(v - round(v)) < 1e-9:
        return str(int(round(v)))
    return ('%g' % v)


def generate(reading=250.0, unit="g", full_scale=1000, major_step=100, size=320,
             dial="fan"):
    reading = float(reading)
    full_scale = float(full_scale)
    major_step = float(major_step)

    if dial == "circle":
        return _generate_circle(reading, unit, full_scale, major_step, size)

    frac = 0.0 if full_scale <= 0 else max(0.0, min(1.0, reading / full_scale))
    needle_angle = START_ANGLE + frac * SWEEP

    cx = size / 2
    cy = size / 2 - 6
    R = size / 2 - 28          # dial face radius
    parts = []
    parts.append(
        f'<svg xmlns="http://www.w3.org/2000/svg" width="{size}" height="{size}" '
        f'viewBox="0 0 {size} {size}">'
    )
    parts.append(STYLE)

    # Border frame
    parts.append(
        f'<rect x="3" y="3" width="{size-6}" height="{size-6}" rx="18" ry="18" '
        f'fill="none" stroke="var(--border)" stroke-width="4"/>'
    )

    # Steel-blue body (rounded square housing)
    parts.append(
        f'<rect x="12" y="12" width="{size-24}" height="{size-24}" rx="28" ry="28" '
        f'fill="var(--steel)"/>'
    )

    # Cream circular dial face
    parts.append(
        f'<circle cx="{cx}" cy="{cy}" r="{R}" fill="var(--cream)" '
        f'stroke="var(--ink)" stroke-width="3"/>'
    )

    # Major ticks every `major_step` (relabelled 0..full_scale), with
    # 5 minor subdivisions between consecutive majors.
    n_majors = 1 if major_step <= 0 else int(round(full_scale / major_step))
    minor_per_major = 5
    minors = max(1, n_majors) * minor_per_major
    for i in range(minors + 1):
        f = i / minors
        ang = START_ANGLE + f * SWEEP
        value = f * full_scale
        is_major = (i % minor_per_major == 0)
        if is_major:
            x1, y1 = _pt(cx, cy, R - 4, ang)
            x2, y2 = _pt(cx, cy, R - 18, ang)
            parts.append(
                f'<line x1="{x1:.2f}" y1="{y1:.2f}" x2="{x2:.2f}" y2="{y2:.2f}" '
                f'stroke="var(--ink)" stroke-width="2.5"/>'
            )
            lx, ly = _pt(cx, cy, R - 32, ang)
            parts.append(
                f'<text x="{lx:.2f}" y="{ly:.2f}" font-family="sans-serif" '
                f'font-size="13" fill="var(--ink)" text-anchor="middle" '
                f'dominant-baseline="central">{_fmt(value)}</text>'
            )
        else:
            x1, y1 = _pt(cx, cy, R - 4, ang)
            x2, y2 = _pt(cx, cy, R - 11, ang)
            parts.append(
                f'<line x1="{x1:.2f}" y1="{y1:.2f}" x2="{x2:.2f}" y2="{y2:.2f}" '
                f'stroke="var(--ink)" stroke-width="1"/>'
            )

    # Crimson needle
    nx, ny = _pt(cx, cy, R - 24, needle_angle)
    # short tail opposite
    tx, ty = _pt(cx, cy, 14, needle_angle + 180)
    parts.append(
        f'<line x1="{tx:.2f}" y1="{ty:.2f}" x2="{nx:.2f}" y2="{ny:.2f}" '
        f'stroke="var(--crimson)" stroke-width="4" stroke-linecap="round"/>'
    )
    parts.append(f'<circle cx="{cx}" cy="{cy}" r="8" fill="var(--crimson)"/>')

    # Small unit label (no instruction text)
    parts.append(
        f'<text x="{cx}" y="{cy + R*0.45:.2f}" font-family="sans-serif" '
        f'font-size="16" font-weight="bold" fill="var(--ink)" '
        f'text-anchor="middle">{unit}</text>'
    )

    parts.append('</svg>')
    return '\n'.join(parts)


def _generate_circle(reading, unit, full_scale, major_step, size):
    """Full 360-deg round dial: 0 at top, labels clockwise."""
    # Needle: clockwise from top, full_scale maps to a complete turn.
    frac = 0.0 if full_scale <= 0 else (reading / full_scale)
    needle_angle = (frac % 1.0) * 360.0

    cx = size / 2
    cy = size / 2 - 6
    R = size / 2 - 28          # dial face radius
    parts = []
    parts.append(
        f'<svg xmlns="http://www.w3.org/2000/svg" width="{size}" height="{size}" '
        f'viewBox="0 0 {size} {size}">'
    )
    parts.append(STYLE)

    # Border frame
    parts.append(
        f'<rect x="3" y="3" width="{size-6}" height="{size-6}" rx="18" ry="18" '
        f'fill="none" stroke="var(--border)" stroke-width="4"/>'
    )

    # Steel-blue body (rounded square housing)
    parts.append(
        f'<rect x="12" y="12" width="{size-24}" height="{size-24}" rx="28" ry="28" '
        f'fill="var(--steel)"/>'
    )

    # Cream circular dial face
    parts.append(
        f'<circle cx="{cx}" cy="{cy}" r="{R}" fill="var(--cream)" '
        f'stroke="var(--ink)" stroke-width="3"/>'
    )

    # Major ticks every `major_step` around the full circle, with 5 minor
    # subdivisions between consecutive majors. The full_scale label coincides
    # with 0 at the top, so we draw majors 0..(full_scale - major_step).
    n_majors = 1 if major_step <= 0 else max(1, int(round(full_scale / major_step)))
    minor_per_major = 5
    minors = n_majors * minor_per_major
    for i in range(minors):                 # 0..minors-1 (i==minors == i==0 at top)
        f = i / minors
        ang = f * 360.0                     # clockwise from top
        value = f * full_scale
        is_major = (i % minor_per_major == 0)
        if is_major:
            x1, y1 = _pt(cx, cy, R - 4, ang)
            x2, y2 = _pt(cx, cy, R - 18, ang)
            parts.append(
                f'<line x1="{x1:.2f}" y1="{y1:.2f}" x2="{x2:.2f}" y2="{y2:.2f}" '
                f'stroke="var(--ink)" stroke-width="2.5"/>'
            )
            lx, ly = _pt(cx, cy, R - 32, ang)
            parts.append(
                f'<text x="{lx:.2f}" y="{ly:.2f}" font-family="sans-serif" '
                f'font-size="13" fill="var(--ink)" text-anchor="middle" '
                f'dominant-baseline="central">{_fmt(value)}</text>'
            )
        else:
            x1, y1 = _pt(cx, cy, R - 4, ang)
            x2, y2 = _pt(cx, cy, R - 11, ang)
            parts.append(
                f'<line x1="{x1:.2f}" y1="{y1:.2f}" x2="{x2:.2f}" y2="{y2:.2f}" '
                f'stroke="var(--ink)" stroke-width="1"/>'
            )

    # Crimson needle from centre
    nx, ny = _pt(cx, cy, R - 24, needle_angle)
    tx, ty = _pt(cx, cy, 14, needle_angle + 180)
    parts.append(
        f'<line x1="{tx:.2f}" y1="{ty:.2f}" x2="{nx:.2f}" y2="{ny:.2f}" '
        f'stroke="var(--crimson)" stroke-width="4" stroke-linecap="round"/>'
    )
    parts.append(f'<circle cx="{cx}" cy="{cy}" r="8" fill="var(--crimson)"/>')

    # Small unit label
    parts.append(
        f'<text x="{cx}" y="{cy + R*0.45:.2f}" font-family="sans-serif" '
        f'font-size="16" font-weight="bold" fill="var(--ink)" '
        f'text-anchor="middle">{unit}</text>'
    )

    parts.append('</svg>')
    return '\n'.join(parts)


if __name__ == '__main__':
    import os
    out = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'samples'))
    os.makedirs(out, exist_ok=True)
    samples = [
        dict(reading=2, unit="kg", full_scale=3, major_step=1),
        dict(reading=450, unit="g", full_scale=800, major_step=200),
        dict(reading=3, unit="kg", full_scale=4, major_step=1),
    ]
    for i, kw in enumerate(samples, 1):
        p = os.path.join(out, f'platform_scale_{i}.svg')
        with open(p, 'w', encoding='utf-8') as f:
            f.write(generate(**kw))
        print('wrote', p)

    circle_samples = [
        dict(reading=3, unit="kg", full_scale=4, major_step=1, dial="circle"),
        dict(reading=2, unit="kg", full_scale=10, major_step=2, dial="circle"),
    ]
    for i, kw in enumerate(circle_samples, 1):
        p = os.path.join(out, f'platform_scale_circle_{i}.svg')
        with open(p, 'w', encoding='utf-8') as f:
            f.write(generate(**kw))
        print('wrote', p)
