#!/usr/bin/env python3
"""Display format mask for every project item.  Shared by both generators.

CI Server's `<format>` is a **digit mask**, not a Java DecimalFormat pattern:

    <format>99.99</format>     ->  two integer digits, always two decimals

That is what CI View wrote when the FIT-201 INLET value was set to 99.99 by
hand, and it matches the `VALUE_FORMAT` masks in the item export
(`"99999"` on our items, `"99.99"` on the reference analog IO).  `9` is a
digit position; the count of positions after the point is the number of
decimals always shown.

The first build emitted `<format>0</format>` and `<format>0.0</format>` -
DecimalFormat patterns, which read as masks **one digit wide**.  Every value
wider than that had nowhere to render.  That, not the missing `<value>`, is
why the displays read 0: `WRPS_TagTest` carried no `<format>` at all and all
98 bindings were live.

So a mask must be **wide enough for the point's full range**, or the value
may not fit.  The masks below are sized from the register map's engineering
ranges, and points of the same kind share a mask so the screens stay
consistent:

    level & speed in %, and their setpoints 999.9
    flows (m3/h, three pumps reach ~1300)   9999.9
    durations & hour counters (s / h)       99999
    volume (m3)                             9999
    alarm bitmask (unsigned 16-bit)         99999
    enums, counts, modes, booleans          9

Decimals follow the point's real resolution, not a fixed two: level in % of
the 6.000 m spill weir resolves to 0.017% because the register is mm, so one
decimal is honest and two would not be; a flow in m3/h resolves to 0.1.
Integer registers get no decimal at all - a decimal place there would be
invented precision.

**These masks must agree with `format_mask` in `04-scada/modbus_points/ci-server-points.csv`,**
which is what the item export carries into CI Server as `VALUE_FORMAT`.  The
units and their gains are defined once, in `gen_scada_points.py`.
"""

# item short name (after "AID.WRPS.") -> mask
MASKS = {
    # --- digital status, one digit ------------------------------------
    "PU301.RUN_CMD": "9",
    "PU302.RUN_CMD": "9",
    "PU303.RUN_CMD": "9",
    "PU301.RUNNING": "9",
    "PU302.RUNNING": "9",
    "PU303.RUNNING": "9",
    "PU301.AVAILABLE": "9",
    "PU302.AVAILABLE": "9",
    "PU303.AVAILABLE": "9",
    "STN.IN_AUTO": "9",
    "STN.HIGH_LEVEL": "9",
    "STN.SPILL_ACTIVE": "9",
    "PU301.TRIPPED": "9",
    "PU302.TRIPPED": "9",
    "PU303.TRIPPED": "9",

    # --- analog measurements ------------------------------------------
    "STN.LEVEL": "999.9",           # %, 100% = the 6.000 m spill weir
    "STN.INFLOW": "9999.9",         # m3/h, raw (L/s x10) x 0.36
    "STN.DISCHARGE": "9999.9",      # m3/h, three pumps reach ~1300
    "STN.PUMPS_RUNNING": "9",       # count 0-3
    "STN.SPEED": "999.9",           # %, 100% = 50 Hz
    "STN.TIME_TO_SPILL": "99999",   # s, 32767 = drawing down
    "STN.TIME_TO_LSHH": "99999",    # s, 32767 = drawing down
    "STN.NET_ACCUM": "9999.9",      # m3/h, SIGNED
    "PU301.RUN_HOURS": "99999",     # h
    "PU302.RUN_HOURS": "99999",
    "PU303.RUN_HOURS": "99999",
    "STN.VOL_TO_SPILL": "9999",     # m3

    # --- enums and words ----------------------------------------------
    "STN.STATE": "9",               # enum 3.1
    "PU301.STATE": "9",             # enum 3.2
    "PU302.STATE": "9",
    "PU303.STATE": "9",
    "STN.DUTY_PUMP": "9",           # 0 = none, 1-3
    "STN.ALARM_WORD": "99999",      # unsigned 16-bit bitmask
    "STN.CMD_ACK": "99",

    # --- setpoints ------------------------------------------------------
    "SP.MODE": "9",                 # 1 = auto, 2 = off
    "SP.CMD_WORD": "99",
    "SP.CMD_PARAM": "9",            # pump number
    "SP.LEVEL_SP": "999.9",         # %, entered in % of spill weir
    "SP.START_DUTY": "999.9",       # %
    "SP.START_P2": "999.9",         # %
    "SP.START_P3": "999.9",         # %
    "SP.STOP_ALL": "999.9",         # %
    "SP.HIGH_ALARM": "999.9",       # %
    "SP.MIN_SPEED": "999.9",        # %, 100% = 50 Hz
    "SP.SERVICE_HRS": "99999",      # h

    # --- simulation ------------------------------------------------------
    "SIM.INFLOW": "9999.9",         # m3/h
    "SIM.SCENARIO": "9",            # 0-3
    "SIM.RESET": "9",               # write 1, self-clearing
    "SIM.TIME_SCALE": "999",        # 1-120
}

PREFIX = "AID.WRPS."


def mask(item):
    """Mask for an item, by full or short name.  Unknown -> widest integer."""
    short = item[len(PREFIX):] if item.startswith(PREFIX) else item
    return MASKS.get(short, "99999")


def decimals(item):
    m = mask(item)
    return len(m.split(".")[1]) if "." in m else 0


def check(names):
    """Fail loudly if an item has no mask - a new point must be given one."""
    missing = [n for n in names
               if (n[len(PREFIX):] if n.startswith(PREFIX) else n) not in MASKS]
    if missing:
        import sys
        sys.exit("no format mask for: %s\n  add it to point_format.py"
                 % ", ".join(missing))
