001package jmri.jmrit.logixng.actions;
002
003import java.util.List;
004import java.util.ArrayList;
005import java.util.HashMap;
006import java.util.Locale;
007import java.util.Map;
008
009import javax.annotation.Nonnull;
010import javax.annotation.CheckForNull;
011
012import jmri.InstanceManager;
013import jmri.JmriException;
014import jmri.Manager;
015import jmri.jmrit.logixng.*;
016import jmri.jmrit.logixng.implementation.DefaultFemaleGenericExpressionSocket;
017import jmri.jmrit.logixng.util.parser.ParserException;
018import jmri.jmrit.logixng.util.parser.RecursiveDescentParser;
019import jmri.jmrit.logixng.util.parser.Variable;
020import jmri.jmrit.logixng.util.parser.GenericExpressionVariable;
021import jmri.jmrit.logixng.util.parser.ExpressionNode;
022
023/**
024 * This action evaluates the formula.
025 *
026 * @author Daniel Bergqvist Copyright 2021
027 */
028public class DigitalFormula extends AbstractDigitalAction implements FemaleSocketListener {
029
030    private String _formula = "";
031    private ExpressionNode _expressionNode;
032    private final List<ExpressionEntry> _expressionEntries = new ArrayList<>();
033    private boolean _disableCheckForUnconnectedSocket = false;
034
035    /**
036     * Create a new instance of Formula with system name and user name.
037     * @param sys the system name
038     * @param user the user name
039     */
040    public DigitalFormula(@Nonnull String sys, @CheckForNull String user) {
041        super(sys, user);
042        _expressionEntries
043                .add(new ExpressionEntry(createFemaleSocket(this, this, getNewSocketName())));
044    }
045
046    /**
047     * Create a new instance of Formula with system name and user name.
048     * @param sys the system name
049     * @param user the user name
050     * @param expressionSystemNames a list of system names for the expressions
051     * this formula uses
052     */
053    public DigitalFormula(@Nonnull String sys, @CheckForNull String user,
054            List<SocketData> expressionSystemNames) {
055        super(sys, user);
056        setExpressionSystemNames(expressionSystemNames);
057    }
058
059    @Override
060    protected String getPreferredSocketPrefix() {
061        return "E";
062    }
063
064    @Override
065    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
066        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
067        String sysName = systemNames.get(getSystemName());
068        String userName = userNames.get(getSystemName());
069        if (sysName == null) sysName = manager.getAutoSystemName();
070        DigitalFormula copy = new DigitalFormula(sysName, userName);
071        copy.setComment(getComment());
072        copy.setNumSockets(getChildCount());
073        copy.setFormula(_formula);
074        return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames);
075    }
076
077    private void setExpressionSystemNames(List<SocketData> systemNames) {
078        if (!_expressionEntries.isEmpty()) {
079            throw new RuntimeException("expression system names cannot be set more than once");
080        }
081
082        for (SocketData socketData : systemNames) {
083            FemaleGenericExpressionSocket socket =
084                    createFemaleSocket(this, this, socketData._socketName);
085//            FemaleGenericExpressionSocket socket =
086//                    InstanceManager.getDefault(AnalogExpressionManager.class)
087//                            .createFemaleSocket(this, this, entry.getKey());
088
089            _expressionEntries.add(new ExpressionEntry(socket, socketData._socketSystemName, socketData._manager));
090        }
091    }
092
093    public String getExpressionSystemName(int index) {
094        return _expressionEntries.get(index)._socketSystemName;
095    }
096
097    public String getExpressionManager(int index) {
098        return _expressionEntries.get(index)._manager;
099    }
100
101    private FemaleGenericExpressionSocket createFemaleSocket(
102            Base parent, FemaleSocketListener listener, String socketName) {
103
104        return new DefaultFemaleGenericExpressionSocket(
105                FemaleGenericExpressionSocket.SocketType.GENERIC, parent, listener, socketName);
106    }
107
108    public final void setFormula(String formula) throws ParserException {
109        Map<String, Variable> variables = new HashMap<>();
110        RecursiveDescentParser parser = new RecursiveDescentParser(variables);
111        for (int i=0; i < getChildCount(); i++) {
112            Variable v = new GenericExpressionVariable((FemaleGenericExpressionSocket)getChild(i));
113            variables.put(v.getName(), v);
114        }
115        _expressionNode = parser.parseExpression(formula);
116        // parseExpression() may throw an exception and we don't want to set
117        // the field _formula until we now parseExpression() has succeeded.
118        _formula = formula;
119    }
120
121    public String getFormula() {
122        return _formula;
123    }
124
125    private void parseFormula() {
126        try {
127            setFormula(_formula);
128        } catch (ParserException e) {
129            log.error("Unexpected exception when parsing the formula", e);
130        }
131    }
132
133    /** {@inheritDoc} */
134    @Override
135    public Category getCategory() {
136        return Category.COMMON;
137    }
138
139    /** {@inheritDoc} */
140    @Override
141    public void execute() throws JmriException {
142
143        if (_formula.isEmpty()) return;
144
145        _expressionNode.calculate(getConditionalNG().getSymbolTable());
146    }
147
148    @Override
149    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
150        return _expressionEntries.get(index)._socket;
151    }
152
153    @Override
154    public int getChildCount() {
155        return _expressionEntries.size();
156    }
157
158    public void setChildCount(int count) {
159        List<FemaleSocket> addList = new ArrayList<>();
160        List<FemaleSocket> removeList = new ArrayList<>();
161
162        // Is there too many children?
163        while (_expressionEntries.size() > count) {
164            int childNo = _expressionEntries.size()-1;
165            FemaleSocket socket = _expressionEntries.get(childNo)._socket;
166            if (socket.isConnected()) {
167                socket.disconnect();
168            }
169            removeList.add(_expressionEntries.get(childNo)._socket);
170            _expressionEntries.remove(childNo);
171        }
172
173        // Is there not enough children?
174        while (_expressionEntries.size() < count) {
175            FemaleGenericExpressionSocket socket =
176                    createFemaleSocket(this, this, getNewSocketName());
177            _expressionEntries.add(new ExpressionEntry(socket));
178            addList.add(socket);
179        }
180        parseFormula();
181        firePropertyChange(Base.PROPERTY_CHILD_COUNT, removeList, addList);
182    }
183
184    @Override
185    public String getShortDescription(Locale locale) {
186        return Bundle.getMessage(locale, "DigitalFormula_Short");
187    }
188
189    @Override
190    public String getLongDescription(Locale locale) {
191        if (_formula.isEmpty()) {
192            return Bundle.getMessage(locale, "DigitalFormula_Long_Empty");
193        } else {
194            return Bundle.getMessage(locale, "DigitalFormula_Long", _formula);
195        }
196    }
197
198    // This method ensures that we have enough of children
199    private void setNumSockets(int num) {
200        List<FemaleSocket> addList = new ArrayList<>();
201
202        // Is there not enough children?
203        while (_expressionEntries.size() < num) {
204            FemaleGenericExpressionSocket socket =
205                    createFemaleSocket(this, this, getNewSocketName());
206            _expressionEntries.add(new ExpressionEntry(socket));
207            addList.add(socket);
208        }
209        parseFormula();
210        firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList);
211    }
212
213    private void checkFreeSocket() {
214        boolean hasFreeSocket = false;
215
216        for (ExpressionEntry entry : _expressionEntries) {
217            hasFreeSocket |= !entry._socket.isConnected();
218        }
219        if (!hasFreeSocket) {
220            FemaleGenericExpressionSocket socket =
221                    createFemaleSocket(this, this, getNewSocketName());
222            _expressionEntries.add(new ExpressionEntry(socket));
223
224            List<FemaleSocket> list = new ArrayList<>();
225            list.add(socket);
226            parseFormula();
227            firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, list);
228        }
229    }
230
231    /** {@inheritDoc} */
232    @Override
233    public boolean isSocketOperationAllowed(int index, FemaleSocketOperation oper) {
234        switch (oper) {
235            case Remove:        // Possible if socket is not connected
236                return ! getChild(index).isConnected();
237            case InsertBefore:
238                return true;    // Always possible
239            case InsertAfter:
240                return true;    // Always possible
241            case MoveUp:
242                return index > 0;   // Possible if not first socket
243            case MoveDown:
244                return index+1 < getChildCount();   // Possible if not last socket
245            default:
246                throw new UnsupportedOperationException("Oper is unknown" + oper.name());
247        }
248    }
249
250    private void insertNewSocket(int index) {
251        FemaleGenericExpressionSocket socket =
252                createFemaleSocket(this, this, getNewSocketName());
253        _expressionEntries.add(index, new ExpressionEntry(socket));
254
255        List<FemaleSocket> addList = new ArrayList<>();
256        addList.add(socket);
257        parseFormula();
258        firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList);
259    }
260
261    private void removeSocket(int index) {
262        List<FemaleSocket> removeList = new ArrayList<>();
263        removeList.add(_expressionEntries.remove(index)._socket);
264        parseFormula();
265        firePropertyChange(Base.PROPERTY_CHILD_COUNT, removeList, null);
266    }
267
268    private void moveSocketDown(int index) {
269        ExpressionEntry temp = _expressionEntries.get(index);
270        _expressionEntries.set(index, _expressionEntries.get(index+1));
271        _expressionEntries.set(index+1, temp);
272
273        List<FemaleSocket> list = new ArrayList<>();
274        list.add(_expressionEntries.get(index)._socket);
275        list.add(_expressionEntries.get(index)._socket);
276        parseFormula();
277        firePropertyChange(Base.PROPERTY_CHILD_REORDER, null, list);
278    }
279
280    /** {@inheritDoc} */
281    @Override
282    public void doSocketOperation(int index, FemaleSocketOperation oper) {
283        switch (oper) {
284            case Remove:
285                if (getChild(index).isConnected()) throw new UnsupportedOperationException("Socket is connected");
286                removeSocket(index);
287                break;
288            case InsertBefore:
289                insertNewSocket(index);
290                break;
291            case InsertAfter:
292                insertNewSocket(index+1);
293                break;
294            case MoveUp:
295                if (index == 0) throw new UnsupportedOperationException("cannot move up first child");
296                moveSocketDown(index-1);
297                break;
298            case MoveDown:
299                if (index+1 == getChildCount()) throw new UnsupportedOperationException("cannot move down last child");
300                moveSocketDown(index);
301                break;
302            default:
303                throw new UnsupportedOperationException("Oper is unknown" + oper.name());
304        }
305    }
306
307    @Override
308    public void connected(FemaleSocket socket) {
309        if (_disableCheckForUnconnectedSocket) return;
310
311        for (ExpressionEntry entry : _expressionEntries) {
312            if (socket == entry._socket) {
313                entry._socketSystemName =
314                        socket.getConnectedSocket().getSystemName();
315            }
316        }
317
318        checkFreeSocket();
319    }
320
321    @Override
322    public void disconnected(FemaleSocket socket) {
323        for (ExpressionEntry entry : _expressionEntries) {
324            if (socket == entry._socket) {
325                entry._socketSystemName = null;
326                break;
327            }
328        }
329    }
330
331    /** {@inheritDoc} */
332    @Override
333    public void socketNameChanged(FemaleSocket socket) {
334        parseFormula();
335    }
336
337    /** {@inheritDoc} */
338    @Override
339    public void setup() {
340        // We don't want to check for unconnected sockets while setup sockets
341        _disableCheckForUnconnectedSocket = true;
342
343        for (ExpressionEntry ee : _expressionEntries) {
344            try {
345                if ( !ee._socket.isConnected()
346                        || !ee._socket.getConnectedSocket().getSystemName()
347                                .equals(ee._socketSystemName)) {
348
349                    String socketSystemName = ee._socketSystemName;
350                    String manager = ee._manager;
351                    ee._socket.disconnect();
352                    if (socketSystemName != null) {
353                        Manager<? extends MaleSocket> m =
354                                InstanceManager.getDefault(LogixNG_Manager.class)
355                                        .getManager(manager);
356                        MaleSocket maleSocket = m.getBySystemName(socketSystemName);
357                        if (maleSocket != null) {
358                            ee._socket.connect(maleSocket);
359                            maleSocket.setup();
360                        } else {
361                            log.error("cannot load digital expression {}", socketSystemName);
362                        }
363                    }
364                } else {
365                    ee._socket.getConnectedSocket().setup();
366                }
367            } catch (SocketAlreadyConnectedException ex) {
368                // This shouldn't happen and is a runtime error if it does.
369                throw new RuntimeException("socket is already connected");
370            }
371        }
372
373        parseFormula();
374        checkFreeSocket();
375
376        _disableCheckForUnconnectedSocket = false;
377    }
378
379    /** {@inheritDoc} */
380    @Override
381    public void registerListenersForThisClass() {
382        // Do nothing
383    }
384
385    /** {@inheritDoc} */
386    @Override
387    public void unregisterListenersForThisClass() {
388        // Do nothing
389    }
390
391    /** {@inheritDoc} */
392    @Override
393    public void disposeMe() {
394    }
395
396
397    public static class SocketData {
398        public final String _socketName;
399        public final String _socketSystemName;
400        public final String _manager;
401
402        public SocketData(String socketName, String socketSystemName, String manager) {
403            _socketName = socketName;
404            _socketSystemName = socketSystemName;
405            _manager = manager;
406        }
407    }
408
409
410    /* This class is public since ExpressionFormulaXml needs to access it. */
411    public static class ExpressionEntry {
412        private final FemaleGenericExpressionSocket _socket;
413        private String _socketSystemName;
414        public String _manager;
415
416        public ExpressionEntry(FemaleGenericExpressionSocket socket, String socketSystemName, String manager) {
417            _socket = socket;
418            _socketSystemName = socketSystemName;
419            _manager = manager;
420        }
421
422        private ExpressionEntry(FemaleGenericExpressionSocket socket) {
423            this._socket = socket;
424        }
425
426    }
427
428
429    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DigitalFormula.class);
430}