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