001package jmri.jmrit.z21server;
002
003import jmri.DccThrottle;
004import jmri.LocoAddress;
005import jmri.ThrottleListener;
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008
009import java.net.InetAddress;
010import java.util.HashMap;
011
012public class ClientManager implements ThrottleListener {
013
014    private static ClientManager instance;
015    private static final HashMap<InetAddress, AppClient> registeredClients = new HashMap<>();
016    private static final HashMap<Integer, InetAddress> requestedThrottlesList = new HashMap<>();
017    public static float speedMultiplier = 1.0f / 128.0f;
018
019    private final static Logger log = LoggerFactory.getLogger(ClientManager.class);
020
021    private ClientManager() {
022    }
023
024    synchronized public static ClientManager getInstance() {
025        if (instance == null) {
026            instance = new ClientManager();
027        }
028        return instance;
029    }
030
031    synchronized public void registerLocoIfNeeded(InetAddress clientAddress, int locoAddress) {
032        if (!registeredClients.containsKey(clientAddress)) {
033            AppClient client = new AppClient(clientAddress);
034            registeredClients.put(clientAddress, client);
035        }
036        if (registeredClients.get(clientAddress).getThrottleFromLocoAddress(locoAddress) == null) {
037            jmri.InstanceManager.throttleManagerInstance().requestThrottle(locoAddress, ClientManager.getInstance());
038            requestedThrottlesList.put(locoAddress, clientAddress);
039        }
040    }
041
042    synchronized public void setLocoSpeedAndDirection(InetAddress clientAddress, int locoAddress, int speed, boolean forward) {
043        AppClient client = registeredClients.get(clientAddress);
044        if (client != null) {
045            DccThrottle throttle = client.getThrottleFromLocoAddress(locoAddress);
046            if (throttle != null) {
047                if (throttle.getIsForward() != forward) throttle.setIsForward(forward);
048                throttle.setSpeedSetting(speed * speedMultiplier);
049            } else {
050                log.info("Unable to find throttle for loco {} from client {}", locoAddress, clientAddress);
051            }
052        } else {
053            log.info("App client {} is not registered", clientAddress);
054        }
055    }
056
057    synchronized public void setLocoFunction(InetAddress clientAddress, int locoAddress, int functionNumber, boolean bOn) {
058        AppClient client = registeredClients.get(clientAddress);
059        if (client != null) {
060            DccThrottle throttle = client.getThrottleFromLocoAddress(locoAddress);
061            if (throttle != null) {
062                throttle.setFunction(functionNumber, bOn);
063            } else {
064                log.info("Unable to find throttle for loco {} from client {}", locoAddress, clientAddress);
065            }
066        } else {
067            log.info("App client {} is not registered", clientAddress);
068        }
069    }
070
071    synchronized public void heartbeat(InetAddress clientAddress) {
072        AppClient client = registeredClients.get(clientAddress);
073        if (client != null) client.heartbeat();
074    }
075
076    synchronized public void handleExpiredClients() {
077        var tempMap = new HashMap<>(registeredClients); // to avoid concurrent modification
078        for (AppClient c : tempMap.values()) {
079            if (c.isTimestampExpired()) registeredClients.remove(c.getAddress());
080        }
081    }
082
083    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "PZLA_PREFER_ZERO_LENGTH_ARRAYS",
084    justification = "Messages can be of any length, null is used to indicate absence of message for caller")
085    synchronized public byte[] getLocoStatusMessage(InetAddress address, Integer locoAddress) {
086        if (registeredClients.containsKey(address)) {
087            AppClient client = registeredClients.get(address);
088            return client.getLocoStatusMessage(locoAddress);
089        } else {
090            return null;
091        }
092    }
093
094    @Override
095    synchronized public void notifyThrottleFound(DccThrottle t) {
096        int locoAddress = t.getLocoAddress().getNumber();
097        InetAddress client = requestedThrottlesList.get(locoAddress);
098        if (client != null) {
099            registeredClients.get(client).addThrottle(locoAddress, t);
100            requestedThrottlesList.remove(locoAddress);
101        }
102    }
103
104    @Override
105    synchronized public void notifyFailedThrottleRequest(LocoAddress address, String reason) {
106        log.info("Unable to get Throttle for loco address {}, reason : {}", address.getNumber(), reason);
107        requestedThrottlesList.remove(address.getNumber());
108    }
109
110    @Override
111    synchronized public void notifyDecisionRequired(LocoAddress address, DecisionType question) {
112        jmri.InstanceManager.throttleManagerInstance().responseThrottleDecision(address, ClientManager.getInstance(), ThrottleListener.DecisionType.SHARE);
113    }
114
115    public static byte xor(byte[] packet) {
116        byte xor = (byte) (packet[0] ^ packet[1]);
117        for (int i = 2; i < (packet.length - 1); i++) {
118            xor = (byte) (xor ^ packet[i]);
119        }
120        return xor;
121    }
122
123}