001package jmri.jmrit.logixng.actions;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.util.Locale;
006import java.util.Map;
007
008import jmri.*;
009import jmri.jmrit.logixng.*;
010import jmri.jmrit.logixng.util.LogixNG_SelectEnum;
011import jmri.jmrit.logixng.util.LogixNG_SelectInteger;
012
013/**
014 * Sets a function on a throttle
015 *
016 * @author Daniel Bergqvist Copyright 2023
017 */
018public final class ActionThrottleFunction extends AbstractDigitalAction
019        implements PropertyChangeListener {
020
021    private SystemConnectionMemo _memo;
022    private ThrottleManager _throttleManager;
023    private ThrottleManager _oldThrottleManager;
024
025    // The throttle if we have one or if a request is sent, null otherwise
026    private DccThrottle _throttle;
027    private ThrottleListener _throttleListener;
028
029    private final LogixNG_SelectInteger _selectAddress = new LogixNG_SelectInteger(this, this);
030    private final LogixNG_SelectInteger _selectFunction = new LogixNG_SelectInteger(this, this);
031    private final LogixNG_SelectEnum<FunctionState> _selectOnOff =
032            new LogixNG_SelectEnum<>(this, FunctionState.values(), FunctionState.On, this);
033
034
035    public ActionThrottleFunction(String sys, String user) {
036        super(sys, user);
037
038        // Set the _throttleManager variable
039        setMemo(null);
040    }
041
042    @Override
043    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
044        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
045        String sysName = systemNames.get(getSystemName());
046        String userName = userNames.get(getSystemName());
047        if (sysName == null) sysName = manager.getAutoSystemName();
048        ActionThrottleFunction copy = new ActionThrottleFunction(sysName, userName);
049        copy.setComment(getComment());
050        copy.setMemo(_memo);
051        _selectAddress.copy(copy._selectAddress);
052        _selectFunction.copy(copy._selectFunction);
053        _selectOnOff.copy(copy._selectOnOff);
054        return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames);
055    }
056
057    public LogixNG_SelectInteger getSelectAddress() {
058        return _selectAddress;
059    }
060
061    public LogixNG_SelectInteger getSelectFunction() {
062        return _selectFunction;
063    }
064
065    public LogixNG_SelectEnum<FunctionState> getSelectOnOff() {
066        return _selectOnOff;
067    }
068
069    public void setMemo(SystemConnectionMemo memo) {
070        assertListenersAreNotRegistered(log, "setMemo");
071        _memo = memo;
072        if (_memo != null) {
073            _throttleManager = _memo.get(jmri.ThrottleManager.class);
074            if (_throttleManager == null) {
075                throw new IllegalArgumentException("Memo "+memo.getUserName()+" doesn't have a ThrottleManager");
076            }
077        } else {
078            _throttleManager = InstanceManager.getDefault(ThrottleManager.class);
079        }
080    }
081
082    public SystemConnectionMemo getMemo() {
083        return _memo;
084    }
085
086    /** {@inheritDoc} */
087    @Override
088    public Category getCategory() {
089        return Category.ITEM;
090    }
091
092    /** {@inheritDoc} */
093    @Override
094    public void execute() throws JmriException {
095
096        ConditionalNG conditionalNG = this.getConditionalNG();
097
098        int currentLocoAddress = -1;
099        int newLocoAddress = -1;
100
101        if (_throttle != null) {
102            currentLocoAddress = _throttle.getLocoAddress().getNumber();
103        }
104
105        newLocoAddress = _selectAddress.evaluateValue(conditionalNG);
106
107        if (_throttleManager != _oldThrottleManager) {
108            currentLocoAddress = -1;    // Force request of new throttle
109            _oldThrottleManager = _throttleManager;
110        }
111
112        if (newLocoAddress != currentLocoAddress) {
113
114            if (_throttle != null) {
115                // Release the loco
116                _throttleManager.releaseThrottle(_throttle, _throttleListener);
117                _throttle = null;
118            }
119
120            if (newLocoAddress != -1) {
121
122                _throttleListener =  new ThrottleListener() {
123                    @Override
124                    public void notifyThrottleFound(DccThrottle t) {
125                        _throttle = t;
126                        executeConditionalNG();
127                    }
128
129                    @Override
130                    public void notifyFailedThrottleRequest(LocoAddress address, String reason) {
131                        log.warn("loco {} cannot be aquired", address.getNumber());
132                    }
133
134                    @Override
135                    public void notifyDecisionRequired(LocoAddress address, ThrottleListener.DecisionType question) {
136                        log.warn("Loco {} cannot be aquired. Decision required.", address.getNumber());
137                    }
138                };
139
140                boolean result = _throttleManager.requestThrottle(newLocoAddress, _throttleListener);
141
142                if (!result) {
143                    log.warn("loco {} cannot be aquired", newLocoAddress);
144                }
145            }
146
147        }
148
149        // We have a throttle if _throttle is not null
150        if (_throttle != null) {
151
152            int function = _selectFunction.evaluateValue(conditionalNG);
153            boolean isFunctionOn = _selectOnOff.evaluateEnum(conditionalNG)._value;
154
155            DccThrottle throttle = _throttle;
156            int func = function;
157            boolean funcState = isFunctionOn;
158            jmri.util.ThreadingUtil.runOnLayoutWithJmriException(() -> {
159                throttle.setFunction(func, funcState);
160            });
161        }
162    }
163
164    private void executeConditionalNG() {
165        if (_listenersAreRegistered) {
166            ConditionalNG c = getConditionalNG();
167            if (c != null) {
168                c.execute();
169            }
170        }
171    }
172
173    @Override
174    public String getShortDescription(Locale locale) {
175        return Bundle.getMessage(locale, "ActionThrottleFunction_Short");
176    }
177
178    @Override
179    public String getLongDescription(Locale locale) {
180        if (_memo != null) {
181            return Bundle.getMessage(locale, "ActionThrottleFunction_LongConnection",
182                    _selectAddress.getDescription(locale),
183                    _selectFunction.getDescription(locale),
184                    _selectOnOff.getDescription(locale),
185                    _memo.getUserName());
186        } else {
187            return Bundle.getMessage(locale, "ActionThrottleFunction_Long",
188                    _selectAddress.getDescription(locale),
189                    _selectFunction.getDescription(locale),
190                    _selectOnOff.getDescription(locale));
191        }
192    }
193
194    /** {@inheritDoc} */
195    @Override
196    public void setup() {
197        // Do nothing
198    }
199
200    /** {@inheritDoc} */
201    @Override
202    public void registerListenersForThisClass() {
203        _listenersAreRegistered = true;
204    }
205
206    /** {@inheritDoc} */
207    @Override
208    public void unregisterListenersForThisClass() {
209        _listenersAreRegistered = false;
210    }
211
212    /** {@inheritDoc} */
213    @Override
214    public void propertyChange(PropertyChangeEvent evt) {
215        getConditionalNG().execute();
216    }
217
218    /** {@inheritDoc} */
219    @Override
220    public void disposeMe() {
221        if (_throttle != null) {
222            _throttleManager.releaseThrottle(_throttle, _throttleListener);
223        }
224    }
225
226    public enum FunctionState {
227        Off(false, Bundle.getMessage("StateOff")),
228        On(true, Bundle.getMessage("StateOn"));
229
230        private final boolean _value;
231        private final String _text;
232
233        private FunctionState(boolean value, String text) {
234            this._value = value;
235            this._text = text;
236        }
237
238        public boolean getValue() {
239            return _value;
240        }
241
242        @Override
243        public String toString() {
244            return _text;
245        }
246
247    }
248
249    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionThrottleFunction.class);
250
251}