001package jmri.jmrit.logixng.actions;
002
003import java.util.*;
004
005import jmri.InstanceManager;
006import jmri.JmriException;
007import jmri.jmrit.logixng.*;
008
009/**
010 * Executes an action when the expression is True.
011 *
012 * @author Daniel Bergqvist Copyright 2018
013 */
014public class IfThenElse extends AbstractDigitalAction
015        implements FemaleSocketListener {
016
017    private ExecuteType _executeType = ExecuteType.ExecuteOnChange;
018    private EvaluateType _evaluateType = EvaluateType.EvaluateAll;
019    private final List<ExpressionEntry> _expressionEntries = new ArrayList<>();
020    private final List<ActionEntry> _actionEntries = new ArrayList<>();
021    private boolean disableCheckForUnconnectedSocket = false;
022
023    public IfThenElse(String sys, String user) {
024        super(sys, user);
025        _expressionEntries
026                .add(new ExpressionEntry(InstanceManager.getDefault(DigitalExpressionManager.class)
027                        .createFemaleSocket(this, this, Bundle.getMessage("IfThenElse_Socket_If"))));
028        _actionEntries
029                .add(new ActionEntry(InstanceManager.getDefault(DigitalActionManager.class)
030                        .createFemaleSocket(this, this, Bundle.getMessage("IfThenElse_Socket_Then"))));
031        _actionEntries
032                .add(new ActionEntry(InstanceManager.getDefault(DigitalActionManager.class)
033                        .createFemaleSocket(this, this, Bundle.getMessage("IfThenElse_Socket_Else"))));
034    }
035
036    public IfThenElse(String sys, String user,
037            List<Map.Entry<String, String>> expressionSystemNames,
038            List<Map.Entry<String, String>> actionSystemNames)
039            throws BadUserNameException, BadSystemNameException {
040        super(sys, user);
041        setExpressionSystemNames(expressionSystemNames);
042        setActionSystemNames(actionSystemNames);
043    }
044
045    public static String getNewSocketName(String propertyName, String[] names) {
046        int x = 1;
047        while (x < 10000) {     // Protect from infinite loop
048            boolean validName = true;
049            String name = Bundle.getMessage(propertyName, x);
050            for (int i=0; i < names.length; i++) {
051                if (name.equals(names[i])) {
052                    validName = false;
053                    break;
054                }
055            }
056            if (validName) {
057                return name;
058            }
059            x++;
060        }
061        throw new RuntimeException("Unable to find a new socket name");
062    }
063
064    public String getNewExpressionSocketName() {
065        String[] names = new String[getChildCount()];
066        for (int i=0; i < getChildCount(); i++) {
067            names[i] = getChild(i).getName();
068        }
069        return getNewSocketName("IfThenElse_Socket_ElseIf", names);
070    }
071
072    public String getNewActionSocketName() {
073        String[] names = new String[getChildCount()];
074        for (int i=0; i < getChildCount(); i++) {
075            names[i] = getChild(i).getName();
076        }
077        return getNewSocketName("IfThenElse_Socket_Then2", names);
078    }
079
080    @Override
081    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
082        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
083        String sysName = systemNames.get(getSystemName());
084        String userName = userNames.get(getSystemName());
085        if (sysName == null) sysName = manager.getAutoSystemName();
086        IfThenElse copy = new IfThenElse(sysName, userName);
087        copy.setComment(getComment());
088        copy.setExecuteType(_executeType);
089        copy.setEvaluateType(_evaluateType);
090
091        // Ensure the copy has as many childs as myself
092        while (copy.getChildCount() < this.getChildCount()) {
093            copy.doSocketOperation(copy.getChildCount()-2, FemaleSocketOperation.InsertAfter);
094        }
095
096        return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames);
097    }
098
099    private void setExpressionSystemNames(List<Map.Entry<String, String>> systemNames) {
100        if (!_expressionEntries.isEmpty()) {
101            throw new RuntimeException("expression system names cannot be set more than once");
102        }
103
104        for (Map.Entry<String, String> entry : systemNames) {
105            FemaleDigitalExpressionSocket socket =
106                    InstanceManager.getDefault(DigitalExpressionManager.class)
107                            .createFemaleSocket(this, this, entry.getKey());
108
109            _expressionEntries.add(new ExpressionEntry(socket, entry.getValue()));
110        }
111    }
112
113    private void setActionSystemNames(List<Map.Entry<String, String>> systemNames) {
114        if (!_actionEntries.isEmpty()) {
115            throw new RuntimeException("action system names cannot be set more than once");
116        }
117
118        for (Map.Entry<String, String> entry : systemNames) {
119            FemaleDigitalActionSocket socket =
120                    InstanceManager.getDefault(DigitalActionManager.class)
121                            .createFemaleSocket(this, this, entry.getKey());
122
123            _actionEntries.add(new ActionEntry(socket, entry.getValue()));
124        }
125    }
126
127    /** {@inheritDoc} */
128    @Override
129    public Category getCategory() {
130        return Category.FLOW_CONTROL;
131    }
132
133    /** {@inheritDoc} */
134    @Override
135    public void execute() throws JmriException {
136        boolean changed = false;
137
138        FemaleDigitalActionSocket socketToExecute = null;
139
140        for (int i=0; i < _expressionEntries.size(); i++) {
141            ExpressionEntry entry = _expressionEntries.get(i);
142            boolean result = entry._socket.evaluate();
143            TriState _expressionResult = TriState.getValue(result);
144
145            // _lastExpressionResult may be Unknown
146            if ((_executeType == ExecuteType.AlwaysExecute) || (_expressionResult != entry._lastExpressionResult)) {
147                changed = true;
148
149                // Last expression result must be stored as a tri state value, since
150                // we must know if the old value is known or not.
151                entry._lastExpressionResult = _expressionResult;
152
153                if (result) {
154                    if (socketToExecute == null) {
155                        socketToExecute = _actionEntries.get(i)._socket;
156                    }
157                    if (_evaluateType == EvaluateType.EvaluateNeeded) {
158                        break;
159                    }
160                }
161            }
162        }
163
164        // If here, all expressions where false
165        if (changed && socketToExecute == null) {
166            socketToExecute = _actionEntries.get(_actionEntries.size()-1)._socket;
167        }
168
169        if (socketToExecute != null) {
170            socketToExecute.execute();
171        } else {
172            log.trace("socketToExecute is null");
173        }
174    }
175
176    /**
177     * Get the execute type.
178     * @return the type
179     */
180    public ExecuteType getExecuteType() {
181        return _executeType;
182    }
183
184    /**
185     * Set the execute type.
186     * @param type the type
187     */
188    public void setExecuteType(ExecuteType type) {
189        _executeType = type;
190    }
191
192    /**
193     * Get the execute type.
194     * @return the type
195     */
196    public EvaluateType getEvaluateType() {
197        return _evaluateType;
198    }
199
200    /**
201     * Set the execute type.
202     * @param type the type
203     */
204    public void setEvaluateType(EvaluateType type) {
205        _evaluateType = type;
206    }
207
208    @Override
209    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
210        if (index+1 > getChildCount()) {
211            throw new IllegalArgumentException(String.format("index has invalid value: %d", index));
212        }
213        if (index+1 == getChildCount()) {
214            return _actionEntries.get(_actionEntries.size()-1)._socket;
215        }
216        if ((index % 2) == 0) {
217            return _expressionEntries.get(index >> 1)._socket;
218        } else {
219            return _actionEntries.get(index >> 1)._socket;
220        }
221    }
222
223    @Override
224    public int getChildCount() {
225        return _expressionEntries.size() + _actionEntries.size();
226    }
227
228    /** {@inheritDoc} */
229    @Override
230    public boolean isSocketOperationAllowed(int index, FemaleSocketOperation oper) {
231        int numChilds = getChildCount();
232
233        switch (oper) {
234            case Remove:
235                // Possible if not the last socket,
236                // if there is more than four sockets,
237                // the socket is not connected and the next socket is not connected
238                return (index >= 0)
239                        && (index+2 < numChilds)
240                        && (numChilds > 4)
241                        && !getChild(index).isConnected()
242                        && !getChild(index+1).isConnected();
243            case InsertBefore:
244                return index >= 0;
245            case InsertAfter:
246                // Possible if not the last socket
247                return index < numChilds-1;
248            case MoveUp:
249                // Possible, except for the first two sockets and the last two sockets
250                return (index >= 2) && (index < numChilds-2);
251            case MoveDown:
252                // Possible if not the last four sockets
253                return (index >= 0) && (index < numChilds-4);
254            default:
255                throw new UnsupportedOperationException("Oper is unknown" + oper.name());
256        }
257    }
258
259    private void insertNewSocket(int index) {
260        int expressionIndex = index >> 1;
261        int actionIndex = index >> 1;
262
263        // Does index points to an action socket instead of an expression socket?
264        if ((index % 2) != 0) {
265            actionIndex = index >> 1;
266            expressionIndex = (index >> 1) + 1;
267        }
268
269        FemaleDigitalExpressionSocket exprSocket =
270                InstanceManager.getDefault(DigitalExpressionManager.class)
271                        .createFemaleSocket(this, this, getNewExpressionSocketName());
272
273        FemaleDigitalActionSocket actionSocket =
274                InstanceManager.getDefault(DigitalActionManager.class)
275                        .createFemaleSocket(this, this, getNewActionSocketName());
276
277        _expressionEntries.add(expressionIndex, new ExpressionEntry(exprSocket));
278        _actionEntries.add(actionIndex, new ActionEntry(actionSocket));
279
280        List<FemaleSocket> addList = new ArrayList<>();
281        addList.add(actionSocket);
282        addList.add(exprSocket);
283        firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList);
284    }
285
286    private void removeSocket(int index) {
287        int actionIndex = index >> 1;
288        int expressionIndex = index >> 1;
289
290        // Does index points to an action socket instead of an expression socket?
291        if ((index % 2) != 0) {
292            expressionIndex = (index >> 1) + 1;
293        }
294
295        List<FemaleSocket> removeList = new ArrayList<>();
296        removeList.add(_actionEntries.remove(actionIndex)._socket);
297        removeList.add(_expressionEntries.remove(expressionIndex)._socket);
298
299        firePropertyChange(Base.PROPERTY_CHILD_COUNT, removeList, null);
300    }
301
302    private void moveSocketDown(int index) {
303        int actionIndex = index >> 1;
304        int expressionIndex = index >> 1;
305
306        // Does index points to an action socket instead of an expression socket?
307        if ((index % 2) != 0) {
308            expressionIndex = (index >> 1) + 1;
309        }
310
311        ActionEntry actionTemp = _actionEntries.get(actionIndex);
312        _actionEntries.set(actionIndex, _actionEntries.get(actionIndex+1));
313        _actionEntries.set(actionIndex+1, actionTemp);
314
315        ExpressionEntry exprTemp = _expressionEntries.get(expressionIndex);
316        _expressionEntries.set(expressionIndex, _expressionEntries.get(expressionIndex+1));
317        _expressionEntries.set(expressionIndex+1, exprTemp);
318
319        List<FemaleSocket> list = new ArrayList<>();
320        list.add(_actionEntries.get(actionIndex)._socket);
321        list.add(_actionEntries.get(actionIndex+1)._socket);
322        list.add(_expressionEntries.get(expressionIndex)._socket);
323        list.add(_expressionEntries.get(expressionIndex+1)._socket);
324        firePropertyChange(Base.PROPERTY_CHILD_REORDER, null, list);
325    }
326
327    /** {@inheritDoc} */
328    @Override
329    public void doSocketOperation(int index, FemaleSocketOperation oper) {
330        switch (oper) {
331            case Remove:
332                if (index+1 >= getChildCount()) throw new UnsupportedOperationException("Cannot remove only the last socket");
333                if (getChild(index).isConnected()) throw new UnsupportedOperationException("Socket is connected");
334                if (getChild(index+1).isConnected()) throw new UnsupportedOperationException("Socket below is connected");
335                removeSocket(index);
336                break;
337            case InsertBefore:
338                insertNewSocket(index);
339                break;
340            case InsertAfter:
341                insertNewSocket(index+1);
342                break;
343            case MoveUp:
344                if (index < 0) throw new UnsupportedOperationException("cannot move up static sockets");
345                if (index <= 1) throw new UnsupportedOperationException("cannot move up first two children");
346                moveSocketDown(index-2);
347                break;
348            case MoveDown:
349                if (index+2 >= getChildCount()) throw new UnsupportedOperationException("cannot move down last two children");
350                moveSocketDown(index);
351                break;
352            default:
353                throw new UnsupportedOperationException("Oper is unknown" + oper.name());
354        }
355    }
356
357    @Override
358    public void connected(FemaleSocket socket) {
359        if (disableCheckForUnconnectedSocket) return;
360
361        for (ExpressionEntry entry : _expressionEntries) {
362            if (socket == entry._socket) {
363                entry._socketSystemName =
364                        socket.getConnectedSocket().getSystemName();
365            }
366        }
367        for (ActionEntry entry : _actionEntries) {
368            if (socket == entry._socket) {
369                entry._socketSystemName =
370                        socket.getConnectedSocket().getSystemName();
371            }
372        }
373    }
374
375    @Override
376    public void disconnected(FemaleSocket socket) {
377        for (ExpressionEntry entry : _expressionEntries) {
378            if (socket == entry._socket) {
379                entry._socketSystemName = null;
380            }
381        }
382        for (ActionEntry entry : _actionEntries) {
383            if (socket == entry._socket) {
384                entry._socketSystemName = null;
385            }
386        }
387    }
388
389    @Override
390    public String getShortDescription(Locale locale) {
391        return Bundle.getMessage(locale, "IfThenElse_Short");
392    }
393
394    @Override
395    public String getLongDescription(Locale locale) {
396        return Bundle.getMessage(locale, "IfThenElse_Long", _executeType.toString());
397    }
398
399    public int getNumExpressions() {
400        return _expressionEntries.size();
401    }
402
403    public FemaleDigitalExpressionSocket getExpressionSocket(int socket) {
404        return _expressionEntries.get(socket)._socket;
405    }
406
407    public String getExpressionSocketSystemName(int socket) {
408        return _expressionEntries.get(socket)._socketSystemName;
409    }
410
411    public void setExpressionSocketSystemName(int socket, String systemName) {
412        _expressionEntries.get(socket)._socketSystemName = systemName;
413    }
414
415    public int getNumActions() {
416        return _actionEntries.size();
417    }
418
419    public FemaleDigitalActionSocket getActionSocket(int socket) {
420        return _actionEntries.get(socket)._socket;
421    }
422
423    public String getActionSocketSystemName(int socket) {
424        return _actionEntries.get(socket)._socketSystemName;
425    }
426
427    public void setActionSocketSystemName(int socket, String systemName) {
428        _actionEntries.get(socket)._socketSystemName = systemName;
429    }
430
431    /** {@inheritDoc} */
432    @Override
433    public void setup() {
434        // We don't want to check for unconnected sockets while setup sockets
435        disableCheckForUnconnectedSocket = true;
436
437        try {
438            for (ExpressionEntry ee : _expressionEntries) {
439                if ( !ee._socket.isConnected()
440                        || !ee._socket.getConnectedSocket().getSystemName()
441                                .equals(ee._socketSystemName)) {
442
443                    String socketSystemName = ee._socketSystemName;
444                    ee._socket.disconnect();
445                    if (socketSystemName != null) {
446                        MaleSocket maleSocket =
447                                InstanceManager.getDefault(DigitalExpressionManager.class)
448                                        .getBySystemName(socketSystemName);
449                        ee._socket.disconnect();
450                        if (maleSocket != null) {
451                            ee._socket.connect(maleSocket);
452                            maleSocket.setup();
453                        } else {
454                            log.error("cannot load digital expression {}", socketSystemName);
455                        }
456                    }
457                } else {
458                    ee._socket.getConnectedSocket().setup();
459                }
460            }
461
462            for (ActionEntry ae : _actionEntries) {
463                if ( !ae._socket.isConnected()
464                        || !ae._socket.getConnectedSocket().getSystemName()
465                                .equals(ae._socketSystemName)) {
466
467                    String socketSystemName = ae._socketSystemName;
468                    ae._socket.disconnect();
469                    if (socketSystemName != null) {
470                        MaleSocket maleSocket =
471                                InstanceManager.getDefault(DigitalActionManager.class)
472                                        .getBySystemName(socketSystemName);
473                        ae._socket.disconnect();
474                        if (maleSocket != null) {
475                            ae._socket.connect(maleSocket);
476                            maleSocket.setup();
477                        } else {
478                            log.error("cannot load digital action {}", socketSystemName);
479                        }
480                    }
481                } else {
482                    ae._socket.getConnectedSocket().setup();
483                }
484            }
485        } catch (SocketAlreadyConnectedException ex) {
486            // This shouldn't happen and is a runtime error if it does.
487            throw new RuntimeException("socket is already connected");
488        }
489
490        disableCheckForUnconnectedSocket = false;
491    }
492
493    /** {@inheritDoc} */
494    @Override
495    public void registerListenersForThisClass() {
496    }
497
498    /** {@inheritDoc} */
499    @Override
500    public void unregisterListenersForThisClass() {
501    }
502
503    /** {@inheritDoc} */
504    @Override
505    public void disposeMe() {
506    }
507
508
509    /**
510     * The type of Action. If the type is changed, the action is aborted if it
511     * is currently running.
512     */
513    public enum ExecuteType {
514        /**
515         * The "then" or "else" action is executed when the expression changes
516         * its result. If the expression has returned "false", but now returns
517         * "true", the "then" action is executed. If the expression has
518         * returned "true", but now returns "false", the "else" action is executed.
519         */
520        ExecuteOnChange(Bundle.getMessage("IfThenElse_ExecuteType_ExecuteOnChange")),
521
522        /**
523         * The "then" or "else" action is always executed when this action is
524         * executed. If the expression returns "true", the "then" action is
525         * executed. If the expression returns "false", the "else" action is
526         * executed.
527         */
528        AlwaysExecute(Bundle.getMessage("IfThenElse_ExecuteType_AlwaysExecute"));
529
530        private final String _text;
531
532        private ExecuteType(String text) {
533            this._text = text;
534        }
535
536        @Override
537        public String toString() {
538            return _text;
539        }
540
541    }
542
543
544    public enum EvaluateType {
545        /**
546         * All the connected expression sockets are evaluated.
547         */
548        EvaluateAll(Bundle.getMessage("IfThenElse_EvaluateType_EvaluateAll")),
549
550        /**
551         * Evaluation starts with the first expression socket and continues
552         * until all sockets are evaluated or the result is known.
553         */
554        EvaluateNeeded(Bundle.getMessage("IfThenElse_EvaluateType_EvaluateNeeded"));
555
556        private final String _text;
557
558        private EvaluateType(String text) {
559            this._text = text;
560        }
561
562        @Override
563        public String toString() {
564            return _text;
565        }
566
567    }
568
569
570    private static enum TriState {
571        Unknown,
572        False,
573        True;
574
575        public static TriState getValue(boolean value) {
576            return value ? True : False;
577        }
578    }
579
580    private static class ExpressionEntry {
581        private String _socketSystemName;
582        private final FemaleDigitalExpressionSocket _socket;
583        private TriState _lastExpressionResult = TriState.Unknown;
584
585        private ExpressionEntry(FemaleDigitalExpressionSocket socket, String socketSystemName) {
586            _socketSystemName = socketSystemName;
587            _socket = socket;
588        }
589
590        private ExpressionEntry(FemaleDigitalExpressionSocket socket) {
591            this._socket = socket;
592        }
593
594    }
595
596    private static class ActionEntry {
597        private String _socketSystemName;
598        private final FemaleDigitalActionSocket _socket;
599
600        private ActionEntry(FemaleDigitalActionSocket socket, String socketSystemName) {
601            _socketSystemName = socketSystemName;
602            _socket = socket;
603        }
604
605        private ActionEntry(FemaleDigitalActionSocket socket) {
606            this._socket = socket;
607        }
608
609    }
610
611
612    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(IfThenElse.class);
613
614}