001package jmri.server.json.loconet;
002
003import static jmri.server.json.loconet.JsonLoconetSlotUsage.LOCONET_SLOT_USAGE;
004
005import com.fasterxml.jackson.databind.JsonNode;
006import com.fasterxml.jackson.databind.ObjectMapper;
007import com.fasterxml.jackson.databind.node.ObjectNode;
008import java.util.List;
009import javax.servlet.http.HttpServletResponse;
010import jmri.InstanceManager;
011import jmri.jmrix.loconet.LnConstants;
012import jmri.jmrix.loconet.LocoNetSlot;
013import jmri.jmrix.loconet.LocoNetSystemConnectionMemo;
014import jmri.jmrix.loconet.SlotManager;
015import jmri.jmrix.loconet.SlotMapEntry.SlotType;
016import jmri.server.json.JsonException;
017import jmri.server.json.JsonHttpService;
018import jmri.server.json.JsonRequest;
019
020/**
021 * Reports real LocoNet loco-slot table usage (used/free/total) for the
022 * active connection's command station.
023 *
024 * Read-only -- iterates the SlotManager's already in-memory slot array,
025 * sending no new LocoNet traffic of its own. "total" reflects the
026 * connected command station's real per-model capacity (from the same
027 * LnCommandStationType data JMRI already uses to auto-detect the
028 * connection), not JMRI's generic upper bound, which is sized for the
029 * largest command station JMRI knows about and would overstate real
030 * capacity for anything smaller.
031 *
032 * @author Andrew Deak Copyright (C) 2026
033 */
034public class JsonLoconetSlotUsageHttpService extends JsonHttpService {
035
036    public JsonLoconetSlotUsageHttpService(ObjectMapper mapper) {
037        super(mapper);
038    }
039
040    @Override
041    public JsonNode doGet(String type, String name, JsonNode data, JsonRequest request) throws JsonException {
042        List<LocoNetSystemConnectionMemo> memos = InstanceManager.getList(LocoNetSystemConnectionMemo.class);
043        if (memos.isEmpty()) {
044            throw new JsonException(503, "No LocoNet connection is available", request.id);
045        }
046        SlotManager sm = memos.get(0).getSlotManager();
047        if (sm == null) {
048            throw new JsonException(503, "No LocoNet slot manager is available", request.id);
049        }
050        int total = 0;
051        int used = 0;
052        for (int i = 0; i < sm.getNumSlots(); i++) {
053            LocoNetSlot s = sm.slot(i);
054            if (s.getSlotType() == SlotType.LOCO) {
055                total++;
056                // An Idle slot (dispatched or released, address still
057                // resident but not actively driven) is capacity the command
058                // station will reclaim for the next engine added. Only
059                // In-Use/Common hold a live, actively-driven address that
060                // isn't available for a different loco.
061                int status = s.slotStatus() & LnConstants.LOCOSTAT_MASK;
062                if (status == LnConstants.LOCO_IN_USE || status == LnConstants.LOCO_COMMON) {
063                    used++;
064                }
065            }
066        }
067        ObjectNode dataNode = mapper.createObjectNode();
068        dataNode.put("commandStation",
069                sm.getCommandStationType() != null ? sm.getCommandStationType().getName() : null);
070        dataNode.put("used", used);
071        dataNode.put("total", total);
072        dataNode.put("free", total - used);
073        return message(LOCONET_SLOT_USAGE, dataNode, request.id);
074    }
075
076    @Override
077    public JsonNode doPost(String type, String name, JsonNode data, JsonRequest request) throws JsonException {
078        // read-only service -- POST behaves the same as GET
079        return doGet(type, name, data, request);
080    }
081
082    @Override
083    public JsonNode doGetList(String type, JsonNode data, JsonRequest request) throws JsonException {
084        // single-object service, no list distinction -- same as GET
085        return doGet(type, "", data, request);
086    }
087
088    @Override
089    public JsonNode doSchema(String type, boolean server, JsonRequest request) throws JsonException {
090        // An unrecognized type must throw, not silently return this
091        // service's own schema regardless of what was asked for -- matches
092        // the pattern used by every other JsonHttpService (e.g.
093        // JsonIdTagHttpService).
094        if (LOCONET_SLOT_USAGE.equals(type)) {
095            return doSchema(type, server,
096                    "jmri/server/json/loconet/loconetSlotUsage-server.json",
097                    "jmri/server/json/loconet/loconetSlotUsage-client.json",
098                    request.id);
099        }
100        throw new JsonException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
101                Bundle.getMessage(request.locale, JsonException.ERROR_UNKNOWN_TYPE, type), request.id);
102    }
103}