# Copyright (C) 2026 Wilhelmus Dingenus de Pooter
# SPDX-License-Identifier: GPL-3.0-or-later
"""
enc_s52_csp.py
--------------
S-52 Conditional Symbology Procedures (CSPs), ported to Python.

Many S-52 LUPT instructions are just ``CS(<procedure>)`` — the real symbology is
computed at runtime from the feature's attributes and the mariner's settings
(safety / shallow / deep contour). This module ports those procedures (the same
ones the S-101 catalogue ships as DEPARE03/SOUNDG03/… and that OpenCPN implements
in s52cnsy.cpp) and ``expand()`` replaces every ``('CS', name)`` instruction with
the procedure's emitted instructions before they reach the interpreter.

Coverage grows over time; an unported CSP yields nothing (the class then shows
whatever direct AC/LS/SY its LUPT also carried, or the engine's neutral default).
The high-value ones are here first: SEABED01 / DEPARE01 (depth-area fill).
"""


def _f(v, default=None):
    try:
        return float(v)
    except (TypeError, ValueError):
        return default


def SEABED01(attrs, ctx):
    """S-52 SEABED01 — pick the depth-shade colour for a depth/dredged area from
    DRVAL1/DRVAL2 against the mariner's shallow/safety/deep contours (four-shade).
    Drying areas (DRVAL1 < 0) get the intertidal shade."""
    drval1 = _f(attrs.get("DRVAL1"), -1.0)
    drval2 = _f(attrs.get("DRVAL2"), (drval1 + 0.01) if drval1 is not None else 0.0)
    shallow = ctx.get("shallow", 2.0)
    safety = ctx.get("safety", 6.0)
    deep = ctx.get("deep", 10.0)
    color = "DEPIT"                                     # intertidal / drying
    if drval1 >= 0 and drval2 > 0:
        color = "DEPVS"
    if drval1 >= shallow and drval2 > shallow:
        color = "DEPMS"
    if drval1 >= safety and drval2 > safety:
        color = "DEPMD"
    if drval1 >= deep and drval2 > deep:
        color = "DEPDW"
    return [("AC", color)]


def DEPARE01(attrs, ctx):
    """S-52 DEPARE01 — depth area: the seabed fill (SEABED01). The safety-contour
    line itself is portrayed by DEPCNT; here we lay the depth shade."""
    return SEABED01(attrs, ctx)


def DRGARE01(attrs, ctx):
    """Dredged area — same depth-shade fill plus its dashed boundary."""
    return SEABED01(attrs, ctx) + [("LS", "DASH", 1.0, "CHGRD")]


_CSP = {
    "SEABED01": SEABED01,
    "DEPARE01": DEPARE01,
    "DEPARE02": DEPARE01,
    "DEPARE03": DEPARE01,
    "DRGARE01": DRGARE01,
}


def has(name):
    return name in _CSP


def run(name, attrs, ctx):
    """Run a CSP → list of primitive instructions ([] if unported/failed)."""
    fn = _CSP.get(name)
    if fn is None:
        return []
    try:
        return fn(attrs or {}, ctx or {})
    except Exception:                                   # noqa: BLE001
        return []


def expand(instructions, attrs, ctx, _depth=0):
    """Replace every ('CS', name) with the procedure's instructions (recursively,
    guarded). Non-CS instructions pass through unchanged."""
    out = []
    for ins in instructions or []:
        if ins and ins[0] == "CS":
            if _depth < 6:
                out.extend(expand(run(ins[1], attrs, ctx), attrs, ctx,
                                  _depth + 1))
        else:
            out.append(ins)
    return out
