001package jmri.jmrit.logixng.actions;
002
003import java.util.ArrayList;
004import java.util.List;
005import java.util.Locale;
006import java.util.Map;
007
008import jmri.InstanceManager;
009import jmri.JmriException;
010import jmri.jmrit.logixng.*;
011
012/**
013 * Execute many Actions in a specific order.
014 * 
015 * @author Daniel Bergqvist Copyright 2018
016 */
017public class DigitalMany extends AbstractDigitalAction
018        implements FemaleSocketListener {
019
020    private final List<ActionEntry> _actionEntries = new ArrayList<>();
021    private boolean disableCheckForUnconnectedSocket = false;
022    
023    public DigitalMany(String sys, String user)
024            throws BadUserNameException, BadSystemNameException {
025        super(sys, user);
026        _actionEntries
027                .add(new ActionEntry(InstanceManager.getDefault(DigitalActionManager.class)
028                        .createFemaleSocket(this, this, getNewSocketName())));
029    }
030
031    public DigitalMany(String sys, String user, List<Map.Entry<String, String>> actionSystemNames)
032            throws BadUserNameException, BadSystemNameException {
033        super(sys, user);
034        setActionSystemNames(actionSystemNames);
035    }
036    
037    @Override
038    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
039        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
040        String sysName = systemNames.get(getSystemName());
041        String userName = userNames.get(getSystemName());
042        if (sysName == null) sysName = manager.getAutoSystemName();
043        DigitalMany copy = new DigitalMany(sysName, userName);
044        copy.setComment(getComment());
045        copy.setNumSockets(getChildCount());
046        return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames);
047    }
048    
049    private void setActionSystemNames(List<Map.Entry<String, String>> systemNames) {
050        if (!_actionEntries.isEmpty()) {
051            throw new RuntimeException("action system names cannot be set more than once");
052        }
053        
054        for (Map.Entry<String, String> entry : systemNames) {
055            FemaleDigitalActionSocket socket =
056                    InstanceManager.getDefault(DigitalActionManager.class)
057                            .createFemaleSocket(this, this, entry.getKey());
058            
059            _actionEntries.add(new ActionEntry(socket, entry.getValue()));
060        }
061    }
062
063    public String getActionSystemName(int index) {
064        return _actionEntries.get(index)._socketSystemName;
065    }
066
067    /** {@inheritDoc} */
068    @Override
069    public void setup() {
070        // We don't want to check for unconnected sockets while setup sockets
071        disableCheckForUnconnectedSocket = true;
072        
073        for (ActionEntry ae : _actionEntries) {
074            try {
075                if ( !ae._socket.isConnected()
076                        || !ae._socket.getConnectedSocket().getSystemName()
077                                .equals(ae._socketSystemName)) {
078
079                    String socketSystemName = ae._socketSystemName;
080                    ae._socket.disconnect();
081                    if (socketSystemName != null) {
082                        MaleSocket maleSocket =
083                                InstanceManager.getDefault(DigitalActionManager.class)
084                                        .getBySystemName(socketSystemName);
085                        if (maleSocket != null) {
086                            ae._socket.connect(maleSocket);
087                            maleSocket.setup();
088                        } else {
089                            log.error("cannot load digital action {}", socketSystemName);
090                        }
091                    }
092                } else {
093                    ae._socket.getConnectedSocket().setup();
094                }
095            } catch (SocketAlreadyConnectedException ex) {
096                // This shouldn't happen and is a runtime error if it does.
097                throw new RuntimeException("socket is already connected");
098            }
099        }
100        
101//        checkFreeSocket();
102        
103        disableCheckForUnconnectedSocket = false;
104    }
105    
106    /** {@inheritDoc} */
107    @Override
108    public Category getCategory() {
109        return Category.COMMON;
110    }
111
112    /** {@inheritDoc} */
113    @Override
114    public void execute() throws JmriException {
115        for (ActionEntry actionEntry : _actionEntries) {
116            actionEntry._socket.execute();
117        }
118    }
119
120    /** {@inheritDoc} */
121    @Override
122    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
123        return _actionEntries.get(index)._socket;
124    }
125
126    /** {@inheritDoc} */
127    @Override
128    public int getChildCount() {
129        return _actionEntries.size();
130    }
131    
132    // This method ensures that we have enough of children
133    private void setNumSockets(int num) {
134        List<FemaleSocket> addList = new ArrayList<>();
135        
136        // Is there not enough children?
137        while (_actionEntries.size() < num) {
138            FemaleDigitalActionSocket socket =
139                    InstanceManager.getDefault(DigitalActionManager.class)
140                            .createFemaleSocket(this, this, getNewSocketName());
141            _actionEntries.add(new ActionEntry(socket));
142            addList.add(socket);
143        }
144        firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList);
145    }
146    
147    private void checkFreeSocket() {
148        boolean hasFreeSocket = false;
149        
150        for (ActionEntry entry : _actionEntries) {
151            hasFreeSocket |= !entry._socket.isConnected();
152        }
153        if (!hasFreeSocket) {
154            FemaleDigitalActionSocket socket =
155                    InstanceManager.getDefault(DigitalActionManager.class)
156                            .createFemaleSocket(this, this, getNewSocketName());
157            _actionEntries.add(new ActionEntry(socket));
158            
159            List<FemaleSocket> list = new ArrayList<>();
160            list.add(socket);
161            firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, list);
162        }
163    }
164    
165    /** {@inheritDoc} */
166    @Override
167    public boolean isSocketOperationAllowed(int index, FemaleSocketOperation oper) {
168        switch (oper) {
169            case Remove:    // Possible if socket is not connected and there are at least two sockets
170                if (_actionEntries.size() == 1) return false;
171                return ! getChild(index).isConnected();
172            case InsertBefore:
173                return true;    // Always possible
174            case InsertAfter:
175                return true;    // Always possible
176            case MoveUp:
177                return index > 0;   // Possible if not first socket
178            case MoveDown:
179                return index+1 < getChildCount();   // Possible if not last socket
180            default:
181                throw new UnsupportedOperationException("Oper is unknown" + oper.name());
182        }
183    }
184    
185    private void insertNewSocket(int index) {
186        FemaleDigitalActionSocket socket =
187                InstanceManager.getDefault(DigitalActionManager.class)
188                        .createFemaleSocket(this, this, getNewSocketName());
189        _actionEntries.add(index, new ActionEntry(socket));
190        
191        List<FemaleSocket> addList = new ArrayList<>();
192        addList.add(socket);
193        firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList);
194    }
195    
196    private void removeSocket(int index) {
197        List<FemaleSocket> removeList = new ArrayList<>();
198        removeList.add(_actionEntries.remove(index)._socket);
199        firePropertyChange(Base.PROPERTY_CHILD_COUNT, removeList, null);
200    }
201    
202    private void moveSocketDown(int index) {
203        ActionEntry temp = _actionEntries.get(index);
204        _actionEntries.set(index, _actionEntries.get(index+1));
205        _actionEntries.set(index+1, temp);
206        
207        List<FemaleSocket> list = new ArrayList<>();
208        list.add(_actionEntries.get(index)._socket);
209        list.add(_actionEntries.get(index+1)._socket);
210        firePropertyChange(Base.PROPERTY_CHILD_REORDER, null, list);
211    }
212    
213    /** {@inheritDoc} */
214    @Override
215    public void doSocketOperation(int index, FemaleSocketOperation oper) {
216        switch (oper) {
217            case Remove:
218                if (getChild(index).isConnected()) throw new UnsupportedOperationException("Socket is connected");
219                removeSocket(index);
220                break;
221            case InsertBefore:
222                insertNewSocket(index);
223                break;
224            case InsertAfter:
225                insertNewSocket(index+1);
226                break;
227            case MoveUp:
228                if (index == 0) throw new UnsupportedOperationException("cannot move up first child");
229                moveSocketDown(index-1);
230                break;
231            case MoveDown:
232                if (index+1 == getChildCount()) throw new UnsupportedOperationException("cannot move down last child");
233                moveSocketDown(index);
234                break;
235            default:
236                throw new UnsupportedOperationException("Oper is unknown" + oper.name());
237        }
238    }
239    
240    /** {@inheritDoc} */
241    @Override
242    public void connected(FemaleSocket socket) {
243        if (disableCheckForUnconnectedSocket) return;
244        
245        for (ActionEntry entry : _actionEntries) {
246            if (socket == entry._socket) {
247                entry._socketSystemName =
248                        socket.getConnectedSocket().getSystemName();
249            }
250        }
251        
252        checkFreeSocket();
253    }
254
255    /** {@inheritDoc} */
256    @Override
257    public void disconnected(FemaleSocket socket) {
258        for (ActionEntry entry : _actionEntries) {
259            if (socket == entry._socket) {
260                entry._socketSystemName = null;
261                break;
262            }
263        }
264    }
265    
266    /** {@inheritDoc} */
267    @Override
268    public String getShortDescription(Locale locale) {
269        return Bundle.getMessage(locale, "Many_Short");
270    }
271
272    /** {@inheritDoc} */
273    @Override
274    public String getLongDescription(Locale locale) {
275        return Bundle.getMessage(locale, "Many_Long");
276    }
277    
278    /** {@inheritDoc} */
279    @Override
280    public void registerListenersForThisClass() {
281        // Do nothing
282    }
283
284    /** {@inheritDoc} */
285    @Override
286    public void unregisterListenersForThisClass() {
287        // Do nothing
288    }
289    
290    /** {@inheritDoc} */
291    @Override
292    public void disposeMe() {
293    }
294    
295    
296    private static class ActionEntry {
297        private String _socketSystemName;
298        private final FemaleDigitalActionSocket _socket;
299        
300        private ActionEntry(FemaleDigitalActionSocket socket, String socketSystemName) {
301            _socketSystemName = socketSystemName;
302            _socket = socket;
303        }
304        
305        private ActionEntry(FemaleDigitalActionSocket socket) {
306            this._socket = socket;
307        }
308        
309    }
310
311    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DigitalMany.class);
312
313}