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 AnalogFormula extends AbstractAnalogExpression 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 AnalogFormula(@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 AnalogFormula(@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        AnalogExpressionManager manager = InstanceManager.getDefault(AnalogExpressionManager.class);
063        String sysName = systemNames.get(getSystemName());
064        String userName = userNames.get(getSystemName());
065        if (sysName == null) sysName = manager.getAutoSystemName();
066        AnalogFormula copy = new AnalogFormula(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 double evaluate() throws JmriException {
138        
139        if (_formula.isEmpty()) {
140            return 0.0;
141        }
142        
143        return TypeConversionUtil.convertToDouble(_expressionNode.calculate(
144                getConditionalNG().getSymbolTable()), false);
145    }
146    
147    @Override
148    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
149        return _expressionEntries.get(index)._socket;
150    }
151    
152    @Override
153    public int getChildCount() {
154        return _expressionEntries.size();
155    }
156    
157    public void setChildCount(int count) {
158        List<FemaleSocket> addList = new ArrayList<>();
159        List<FemaleSocket> removeList = new ArrayList<>();
160        
161        // Is there too many children?
162        while (_expressionEntries.size() > count) {
163            int childNo = _expressionEntries.size()-1;
164            FemaleSocket socket = _expressionEntries.get(childNo)._socket;
165            if (socket.isConnected()) {
166                socket.disconnect();
167            }
168            removeList.add(_expressionEntries.get(childNo)._socket);
169            _expressionEntries.remove(childNo);
170        }
171        
172        // Is there not enough children?
173        while (_expressionEntries.size() < count) {
174            FemaleGenericExpressionSocket socket =
175                    createFemaleSocket(this, this, getNewSocketName());
176            _expressionEntries.add(new ExpressionEntry(socket));
177            addList.add(socket);
178        }
179        parseFormula();
180        firePropertyChange(Base.PROPERTY_CHILD_COUNT, removeList, addList);
181    }
182    
183    @Override
184    public String getShortDescription(Locale locale) {
185        return Bundle.getMessage(locale, "AnalogFormula_Short");
186    }
187    
188    @Override
189    public String getLongDescription(Locale locale) {
190        if (_formula.isEmpty()) {
191            return Bundle.getMessage(locale, "AnalogFormula_Long_Empty");
192        } else {
193            return Bundle.getMessage(locale, "AnalogFormula_Long", _formula);
194        }
195    }
196
197    // This method ensures that we have enough of children
198    private void setNumSockets(int num) {
199        List<FemaleSocket> addList = new ArrayList<>();
200        
201        // Is there not enough children?
202        while (_expressionEntries.size() < num) {
203            FemaleGenericExpressionSocket socket =
204                    createFemaleSocket(this, this, getNewSocketName());
205            _expressionEntries.add(new ExpressionEntry(socket));
206            addList.add(socket);
207        }
208        parseFormula();
209        firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList);
210    }
211    
212    private void checkFreeSocket() {
213        boolean hasFreeSocket = false;
214        
215        for (ExpressionEntry entry : _expressionEntries) {
216            hasFreeSocket |= !entry._socket.isConnected();
217        }
218        if (!hasFreeSocket) {
219            FemaleGenericExpressionSocket socket =
220                    createFemaleSocket(this, this, getNewSocketName());
221            _expressionEntries.add(new ExpressionEntry(socket));
222            
223            List<FemaleSocket> list = new ArrayList<>();
224            list.add(socket);
225            parseFormula();
226            firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, list);
227        }
228    }
229    
230    /** {@inheritDoc} */
231    @Override
232    public boolean isSocketOperationAllowed(int index, FemaleSocketOperation oper) {
233        switch (oper) {
234            case Remove:        // Possible if socket is not connected
235                return ! getChild(index).isConnected();
236            case InsertBefore:
237                return true;    // Always possible
238            case InsertAfter:
239                return true;    // Always possible
240            case MoveUp:
241                return index > 0;   // Possible if not first socket
242            case MoveDown:
243                return index+1 < getChildCount();   // Possible if not last socket
244            default:
245                throw new UnsupportedOperationException("Oper is unknown" + oper.name());
246        }
247    }
248    
249    private void insertNewSocket(int index) {
250        FemaleGenericExpressionSocket socket =
251                createFemaleSocket(this, this, getNewSocketName());
252        _expressionEntries.add(index, new ExpressionEntry(socket));
253        
254        List<FemaleSocket> addList = new ArrayList<>();
255        addList.add(socket);
256        parseFormula();
257        firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList);
258    }
259    
260    private void removeSocket(int index) {
261        List<FemaleSocket> removeList = new ArrayList<>();
262        removeList.add(_expressionEntries.remove(index)._socket);
263        parseFormula();
264        firePropertyChange(Base.PROPERTY_CHILD_COUNT, removeList, null);
265    }
266    
267    private void moveSocketDown(int index) {
268        ExpressionEntry temp = _expressionEntries.get(index);
269        _expressionEntries.set(index, _expressionEntries.get(index+1));
270        _expressionEntries.set(index+1, temp);
271        
272        List<FemaleSocket> list = new ArrayList<>();
273        list.add(_expressionEntries.get(index)._socket);
274        list.add(_expressionEntries.get(index)._socket);
275        parseFormula();
276        firePropertyChange(Base.PROPERTY_CHILD_REORDER, null, list);
277    }
278    
279    /** {@inheritDoc} */
280    @Override
281    public void doSocketOperation(int index, FemaleSocketOperation oper) {
282        switch (oper) {
283            case Remove:
284                if (getChild(index).isConnected()) throw new UnsupportedOperationException("Socket is connected");
285                removeSocket(index);
286                break;
287            case InsertBefore:
288                insertNewSocket(index);
289                break;
290            case InsertAfter:
291                insertNewSocket(index+1);
292                break;
293            case MoveUp:
294                if (index == 0) throw new UnsupportedOperationException("cannot move up first child");
295                moveSocketDown(index-1);
296                break;
297            case MoveDown:
298                if (index+1 == getChildCount()) throw new UnsupportedOperationException("cannot move down last child");
299                moveSocketDown(index);
300                break;
301            default:
302                throw new UnsupportedOperationException("Oper is unknown" + oper.name());
303        }
304    }
305    
306    @Override
307    public void connected(FemaleSocket socket) {
308        if (_disableCheckForUnconnectedSocket) return;
309        
310        for (ExpressionEntry entry : _expressionEntries) {
311            if (socket == entry._socket) {
312                entry._socketSystemName =
313                        socket.getConnectedSocket().getSystemName();
314                entry._manager =
315                        socket.getConnectedSocket().getManager().getClass().getName();
316            }
317        }
318        
319        checkFreeSocket();
320    }
321
322    @Override
323    public void disconnected(FemaleSocket socket) {
324        for (ExpressionEntry entry : _expressionEntries) {
325            if (socket == entry._socket) {
326                entry._socketSystemName = null;
327                entry._manager = null;
328                break;
329            }
330        }
331    }
332    
333    /** {@inheritDoc} */
334    @Override
335    public void socketNameChanged(FemaleSocket socket) {
336        parseFormula();
337    }
338    
339    /** {@inheritDoc} */
340    @Override
341    public void setup() {
342        // We don't want to check for unconnected sockets while setup sockets
343        _disableCheckForUnconnectedSocket = true;
344        
345        for (ExpressionEntry ee : _expressionEntries) {
346            try {
347                if ( !ee._socket.isConnected()
348                        || !ee._socket.getConnectedSocket().getSystemName()
349                                .equals(ee._socketSystemName)) {
350
351                    String socketSystemName = ee._socketSystemName;
352                    String manager = ee._manager;
353                    ee._socket.disconnect();
354                    if (socketSystemName != null) {
355                        Manager<? extends MaleSocket> m =
356                                InstanceManager.getDefault(LogixNG_Manager.class)
357                                        .getManager(manager);
358                        MaleSocket maleSocket = m.getBySystemName(socketSystemName);
359                        if (maleSocket != null) {
360                            ee._socket.connect(maleSocket);
361                            maleSocket.setup();
362                        } else {
363                            log.error("cannot load analog expression " + socketSystemName);
364                        }
365                    }
366                } else {
367                    ee._socket.getConnectedSocket().setup();
368                }
369            } catch (SocketAlreadyConnectedException ex) {
370                // This shouldn't happen and is a runtime error if it does.
371                throw new RuntimeException("socket is already connected");
372            }
373        }
374        
375        parseFormula();
376        checkFreeSocket();
377        
378        _disableCheckForUnconnectedSocket = false;
379    }
380    
381    /** {@inheritDoc} */
382    @Override
383    public void registerListenersForThisClass() {
384        // Do nothing
385    }
386    
387    /** {@inheritDoc} */
388    @Override
389    public void unregisterListenersForThisClass() {
390        // Do nothing
391    }
392    
393    /** {@inheritDoc} */
394    @Override
395    public void disposeMe() {
396    }
397    
398    
399    public static class SocketData {
400        public final String _socketName;
401        public final String _socketSystemName;
402        public final String _manager;
403        
404        public SocketData(String socketName, String socketSystemName, String manager) {
405            _socketName = socketName;
406            _socketSystemName = socketSystemName;
407            _manager = manager;
408        }
409    }
410    
411    
412    /* This class is public since ExpressionFormulaXml needs to access it. */
413    public static class ExpressionEntry {
414        private final FemaleGenericExpressionSocket _socket;
415        private String _socketSystemName;
416        public String _manager;
417        
418        public ExpressionEntry(FemaleGenericExpressionSocket socket, String socketSystemName, String manager) {
419            _socket = socket;
420            _socketSystemName = socketSystemName;
421            _manager = manager;
422        }
423        
424        private ExpressionEntry(FemaleGenericExpressionSocket socket) {
425            this._socket = socket;
426        }
427        
428    }
429    
430    
431    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AnalogFormula.class);
432}