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 DigitalBooleanMany extends AbstractDigitalBooleanAction
018        implements FemaleSocketListener {
019
020    private final List<ActionEntry> _actionEntries = new ArrayList<>();
021    private boolean disableCheckForUnconnectedSocket = false;
022
023    public DigitalBooleanMany(String sys, String user)
024            throws BadUserNameException, BadSystemNameException {
025        super(sys, user);
026        _actionEntries
027                .add(new ActionEntry(InstanceManager.getDefault(DigitalBooleanActionManager.class)
028                        .createFemaleSocket(this, this, getNewSocketName())));
029    }
030
031    public DigitalBooleanMany(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        DigitalBooleanActionManager manager = InstanceManager.getDefault(DigitalBooleanActionManager.class);
040        String sysName = systemNames.get(getSystemName());
041        String userName = userNames.get(getSystemName());
042        if (sysName == null) sysName = manager.getAutoSystemName();
043        DigitalBooleanMany copy = new DigitalBooleanMany(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            FemaleDigitalBooleanActionSocket socket =
056                    InstanceManager.getDefault(DigitalBooleanActionManager.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(DigitalBooleanActionManager.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(boolean value) throws JmriException {
115        for (ActionEntry actionEntry : _actionEntries) {
116            actionEntry._socket.execute(value);
117        }
118    }
119
120    @Override
121    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
122        return _actionEntries.get(index)._socket;
123    }
124
125    @Override
126    public int getChildCount() {
127        return _actionEntries.size();
128    }
129
130    // This method ensures that we have enough of children
131    private void setNumSockets(int num) {
132        List<FemaleSocket> addList = new ArrayList<>();
133
134        // Is there not enough children?
135        while (_actionEntries.size() < num) {
136            FemaleDigitalBooleanActionSocket socket =
137                    InstanceManager.getDefault(DigitalBooleanActionManager.class)
138                            .createFemaleSocket(this, this, getNewSocketName());
139            _actionEntries.add(new ActionEntry(socket));
140            addList.add(socket);
141        }
142        firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList);
143    }
144
145    private void checkFreeSocket() {
146        boolean hasFreeSocket = false;
147
148        for (ActionEntry entry : _actionEntries) {
149            hasFreeSocket |= !entry._socket.isConnected();
150        }
151        if (!hasFreeSocket) {
152            FemaleDigitalBooleanActionSocket socket =
153                    InstanceManager.getDefault(DigitalBooleanActionManager.class)
154                            .createFemaleSocket(this, this, getNewSocketName());
155            _actionEntries.add(new ActionEntry(socket));
156
157            List<FemaleSocket> list = new ArrayList<>();
158            list.add(socket);
159            firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, list);
160        }
161    }
162
163    /** {@inheritDoc} */
164    @Override
165    public boolean isSocketOperationAllowed(int index, FemaleSocketOperation oper) {
166        switch (oper) {
167            case Remove:        // Possible if socket is not connected
168                return ! getChild(index).isConnected();
169            case InsertBefore:
170                return true;    // Always possible
171            case InsertAfter:
172                return true;    // Always possible
173            case MoveUp:
174                return index > 0;   // Possible if not first socket
175            case MoveDown:
176                return index+1 < getChildCount();   // Possible if not last socket
177            default:
178                throw new UnsupportedOperationException("Oper is unknown" + oper.name());
179        }
180    }
181
182    private void insertNewSocket(int index) {
183        FemaleDigitalBooleanActionSocket socket =
184                InstanceManager.getDefault(DigitalBooleanActionManager.class)
185                        .createFemaleSocket(this, this, getNewSocketName());
186        _actionEntries.add(index, new ActionEntry(socket));
187
188        List<FemaleSocket> addList = new ArrayList<>();
189        addList.add(socket);
190        firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList);
191    }
192
193    private void removeSocket(int index) {
194        List<FemaleSocket> removeList = new ArrayList<>();
195        removeList.add(_actionEntries.remove(index)._socket);
196        firePropertyChange(Base.PROPERTY_CHILD_COUNT, removeList, null);
197    }
198
199    private void moveSocketDown(int index) {
200        ActionEntry temp = _actionEntries.get(index);
201        _actionEntries.set(index, _actionEntries.get(index+1));
202        _actionEntries.set(index+1, temp);
203
204        List<FemaleSocket> list = new ArrayList<>();
205        list.add(_actionEntries.get(index)._socket);
206        list.add(_actionEntries.get(index+1)._socket);
207        firePropertyChange(Base.PROPERTY_CHILD_REORDER, null, list);
208    }
209
210    /** {@inheritDoc} */
211    @Override
212    public void doSocketOperation(int index, FemaleSocketOperation oper) {
213        switch (oper) {
214            case Remove:
215                if (getChild(index).isConnected()) throw new UnsupportedOperationException("Socket is connected");
216                removeSocket(index);
217                break;
218            case InsertBefore:
219                insertNewSocket(index);
220                break;
221            case InsertAfter:
222                insertNewSocket(index+1);
223                break;
224            case MoveUp:
225                if (index == 0) throw new UnsupportedOperationException("cannot move up first child");
226                moveSocketDown(index-1);
227                break;
228            case MoveDown:
229                if (index+1 == getChildCount()) throw new UnsupportedOperationException("cannot move down last child");
230                moveSocketDown(index);
231                break;
232            default:
233                throw new UnsupportedOperationException("Oper is unknown" + oper.name());
234        }
235    }
236
237    @Override
238    public void connected(FemaleSocket socket) {
239        if (disableCheckForUnconnectedSocket) return;
240
241        for (ActionEntry entry : _actionEntries) {
242            if (socket == entry._socket) {
243                entry._socketSystemName =
244                        socket.getConnectedSocket().getSystemName();
245            }
246        }
247
248        checkFreeSocket();
249    }
250
251    @Override
252    public void disconnected(FemaleSocket socket) {
253        for (ActionEntry entry : _actionEntries) {
254            if (socket == entry._socket) {
255                entry._socketSystemName = null;
256                break;
257            }
258        }
259    }
260
261    @Override
262    public String getShortDescription(Locale locale) {
263        return Bundle.getMessage(locale, "Many_Short");
264    }
265
266    @Override
267    public String getLongDescription(Locale locale) {
268        return Bundle.getMessage(locale, "Many_Long");
269    }
270
271    /** {@inheritDoc} */
272    @Override
273    public void registerListenersForThisClass() {
274        // Do nothing
275    }
276
277    /** {@inheritDoc} */
278    @Override
279    public void unregisterListenersForThisClass() {
280        // Do nothing
281    }
282
283    /** {@inheritDoc} */
284    @Override
285    public void disposeMe() {
286    }
287
288
289    private static class ActionEntry {
290        private String _socketSystemName;
291        private final FemaleDigitalBooleanActionSocket _socket;
292
293        private ActionEntry(FemaleDigitalBooleanActionSocket socket, String socketSystemName) {
294            _socketSystemName = socketSystemName;
295            _socket = socket;
296        }
297
298        private ActionEntry(FemaleDigitalBooleanActionSocket socket) {
299            this._socket = socket;
300        }
301
302    }
303
304
305    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DigitalBooleanMany.class);
306
307}