001package jmri.jmrit.logix;
002
003import java.util.ArrayList;
004import java.util.HashMap;
005import java.util.Iterator;
006import java.util.List;
007import java.util.Map;
008import java.util.TreeMap;
009
010import javax.annotation.Nonnull;
011
012import jmri.InstanceManager;
013import jmri.NamedBean;
014import jmri.ShutDownTask;
015import jmri.jmrit.roster.RosterEntry;
016import jmri.jmrit.roster.RosterSpeedProfile;
017import jmri.jmrit.roster.RosterSpeedProfile.SpeedStep;
018import jmri.jmrix.internal.InternalSystemConnectionMemo;
019import jmri.managers.AbstractManager;
020import jmri.util.ThreadingUtil;
021import jmri.util.swing.JmriJOptionPane;
022
023/**
024 * Basic Implementation of a WarrantManager.
025 * <p>
026 * Note this is a concrete class.
027 *
028 * @author Pete Cressman Copyright (C) 2009
029 */
030public class WarrantManager extends AbstractManager<Warrant>
031        implements jmri.InstanceManagerAutoDefault {
032    
033    private HashMap<String, RosterSpeedProfile> _mergeProfiles = new HashMap<>();
034    ShutDownTask _shutDownTask = null;
035    private boolean _suppressWarnings = false;
036
037    public WarrantManager() {
038        super(InstanceManager.getDefault(InternalSystemConnectionMemo.class));
039    }
040
041    @Override
042    public int getXMLOrder() {
043        return jmri.Manager.WARRANTS;
044    }
045
046    @Override
047    public char typeLetter() {
048        return 'W';
049    }
050
051    /**
052     * Method to create a new Warrant if it does not exist.
053     * <p>
054     * Returns null if a Warrant with the same systemName or userName already 
055     * exists, or if there is trouble creating a new Warrant.
056     *
057     * @param systemName the system name.
058     * @param userName   the user name.
059     * @param SCWa       true for a new SCWarrant, false for a new Warrant.
060     * @param TTP        the time to platform.
061     * @return an existing warrant if found or a new warrant, may be null.
062     */
063    public Warrant createNewWarrant(String systemName, String userName, boolean SCWa, long TTP) {
064        log.debug("createNewWarrant {} SCWa= {}",systemName,SCWa);
065        // Check that Warrant does not already exist
066        Warrant r;
067        if (userName != null && userName.trim().length() > 0) {
068            r = getByUserName(userName);
069            if (r == null) {
070                r = getBySystemName(systemName);
071            }
072            if (r != null) {
073                log.warn("Warrant {}  exits.",r.getDisplayName());
074                return null;
075            }
076        }
077        if (!systemName.startsWith(getSystemNamePrefix()) || systemName.length() < getSystemNamePrefix().length()+1) {
078            log.error("Warrant system name \"{}\" must begin with \"{}\".",
079                    systemName, getSystemNamePrefix());
080            return null;
081        }
082        // Warrant does not exist, create a new Warrant
083        if (SCWa) {
084            r = new SCWarrant(systemName, userName, TTP);
085        } else {
086            r = new Warrant(systemName, userName);
087        }
088        // save in the maps
089        register(r);
090        return r;
091    }
092
093    /**
094     * Method to get an existing Warrant. First looks up assuming that name is a
095     * User Name. If this fails looks up assuming that name is a System Name. If
096     * both fail, returns null.
097     *
098     * @param name the system name or user name
099     * @return the warrant if found or null
100     */
101    public Warrant getWarrant(String name) {
102        Warrant r = getByUserName(name);
103        if (r != null) {
104            return r;
105        }
106        return getBySystemName(name);
107    }
108
109    public Warrant provideWarrant(String name) {
110        if (name == null || name.trim().length() == 0) {
111            return null;
112        }
113        Warrant w = getByUserName(name);
114        if (w == null) {
115            w = getBySystemName(name);
116        }
117        if (w == null) {
118            w = createNewWarrant(name, null, false, 0);
119        }
120        return w;
121    }
122
123    protected boolean okToRemoveBlock(OBlock block) {
124        String name = block.getDisplayName();
125        List<Warrant> list = warrantsUsing(block);
126        boolean ok = true;
127        if (!list.isEmpty()) {
128//            ok = false;   Last setting was OK = true when _suppressWarnings was set to true
129            if (!_suppressWarnings) {
130                StringBuilder sb = new StringBuilder();
131                for (Warrant w : list) {
132                    sb.append(Bundle.getMessage("DeleteWarrantBlock", name, w.getDisplayName()));
133                }
134                sb.append(Bundle.getMessage("DeleteConfirm", name));
135                ok = okToRemove(name, sb.toString());
136            }
137        }
138        if (ok) {
139            removeWarrants(list);
140        }
141        return ok;
142    }
143    
144    protected boolean okToRemovePortal(Portal portal) {
145        String name = portal.getName();
146        boolean ok = true;
147        List<Warrant> wList = warrantsUsing(portal);
148        if (!wList.isEmpty()) {
149//          ok = false;   Last setting was OK = true when _suppressWarnings was set to true
150            if (!_suppressWarnings) {
151                StringBuilder sb = new StringBuilder();
152                for (Warrant w : wList) {
153                    sb.append(Bundle.getMessage("DeleteWarrantPortal", name, w.getDisplayName()));
154                 }
155                sb.append(Bundle.getMessage("DeleteConfirm", name));
156                ok = okToRemove(name, sb.toString());
157            }
158        }
159        List<NamedBean> sList = signalsUsing(portal);
160        if (!sList.isEmpty()) {
161//          ok = false;   Last setting was OK = true when _suppressWarnings was set to true
162            if (!_suppressWarnings) {
163                StringBuilder sb = new StringBuilder();
164                for (NamedBean s : sList) {
165                    sb.append(Bundle.getMessage("DeletePortalSignal", 
166                            name, s.getDisplayName(), portal.getProtectedBlock(s)));
167                 }
168                sb.append(Bundle.getMessage("DeleteConfirmSignal", name));
169                ok = okToRemove(name, sb.toString());
170            }
171        }
172        
173        if (ok) {
174            removeWarrants(wList);
175            for (NamedBean s : sList) {
176                portal.deleteSignal(s);
177            }
178        }
179        return ok;
180    }
181
182    protected boolean okToRemoveBlockPath(OBlock block, OPath path) {
183        String pathName = path.getName();
184        String blockName = block.getDisplayName();
185        boolean ok = true;
186        List<Warrant> list = warrantsUsing(block, path);
187        if (!list.isEmpty()) {
188//          ok = false;   Last setting was OK = true when _suppressWarnings was set to true
189            if (!_suppressWarnings) {
190                StringBuilder sb = new StringBuilder();
191                for (Warrant w : list) {
192                    sb.append(Bundle.getMessage("DeleteWarrantPath", 
193                            pathName, blockName, w.getDisplayName()));
194                 }
195                sb.append(Bundle.getMessage("DeleteConfirm", pathName));
196                ok = okToRemove(pathName, sb.toString());
197            }
198        }
199        if (ok) {
200            removeWarrants(list);
201        }
202        return ok;
203    }
204
205    private void removeWarrants(List<Warrant> list) {
206        for (Warrant w : list) {
207            if (w.getRunMode() != Warrant.MODE_NONE) {
208                w.controlRunTrain(Warrant.ABORT);
209            }
210            deregister(w);
211            w.dispose();
212        }
213    }
214
215    private boolean okToRemove(String name, String message) {
216        if (!ThreadingUtil.isLayoutThread()) {  //need GUI
217            log.warn("Cannot delete portal \"{}\" from this thread", name);
218            return false;
219        }
220        int val = JmriJOptionPane.showOptionDialog(null, message,
221                Bundle.getMessage("WarningTitle"), JmriJOptionPane.DEFAULT_OPTION,
222                JmriJOptionPane.QUESTION_MESSAGE, null,
223                new Object[]{Bundle.getMessage("ButtonYes"),
224                        Bundle.getMessage("ButtonYesPlus"),
225                        Bundle.getMessage("ButtonNo"),},
226                Bundle.getMessage("ButtonNo")); // default NO
227        if (val == 2 || val == JmriJOptionPane.CLOSED_OPTION ) { // array position 2 No, or Dialog closed
228            return false;
229        }
230        if (val == 1) { // array position 1 ButtonYesPlus suppress future warnings
231            _suppressWarnings = true;
232        }
233        return true;
234    }
235
236    synchronized protected void portalNameChange(String oldName, String newName) {
237        for (Warrant w : getNamedBeanSet()) {
238            List<BlockOrder> orders = w.getBlockOrders();
239            Iterator<BlockOrder> it = orders.iterator();
240            while (it.hasNext()) {
241                BlockOrder bo = it.next();
242                if (oldName.equals(bo.getEntryName())) {
243                    bo.setEntryName(newName);
244                }
245                if (oldName.equals(bo.getExitName())) {
246                    bo.setExitName(newName);
247                }
248            }
249        }
250    }
251
252    protected List<Warrant> warrantsUsing(OBlock block) {
253        ArrayList<Warrant> list = new ArrayList<>();
254        for (Warrant w : getNamedBeanSet()) {
255            List<BlockOrder> orders = w.getBlockOrders();
256            Iterator<BlockOrder> it = orders.iterator();
257            while (it.hasNext()) {
258                if (block.equals(it.next().getBlock()))
259                    list.add(w);
260            }
261        }
262        return list;
263    }
264
265    protected List<Warrant> warrantsUsing(Portal portal) {
266        ArrayList<Warrant> list = new ArrayList<>();
267        String name = portal.getName();
268        for (Warrant w : getNamedBeanSet()) {
269            List<BlockOrder> orders = w.getBlockOrders();
270            Iterator<BlockOrder> it = orders.iterator();
271            while (it.hasNext()) {
272                BlockOrder bo = it.next();
273                if (name.equals(bo.getEntryName()) && !list.contains(w)) {
274                    list.add(w);
275                } else if (name.equals(bo.getExitName()) && !list.contains(w)) {
276                    list.add(w);
277                }
278            }
279        }
280        return list;
281    }
282
283    protected List<NamedBean> signalsUsing(Portal portal) {
284        ArrayList<NamedBean> list = new ArrayList<>();
285        NamedBean signal = portal.getToSignal();
286        if (signal != null) {
287            list.add(signal);
288        }
289        signal = portal.getFromSignal();
290        if (signal != null) {
291            list.add(signal);
292        }
293        return list;
294    }
295
296    protected List<Warrant> warrantsUsing(OBlock block, OPath path) {
297        ArrayList<Warrant> list = new ArrayList<>();
298        String name = path.getName();
299        for (Warrant w : getNamedBeanSet()) {
300            List<BlockOrder> orders = w.getBlockOrders();
301            Iterator<BlockOrder> it = orders.iterator();
302            while (it.hasNext()) {
303                BlockOrder bo = it.next();
304                if (block.equals(bo.getBlock()) && name.equals(bo.getPathName())) {
305                    list.add(w);
306                }
307            }
308        }
309        return list;
310    }
311
312    synchronized protected void pathNameChange(OBlock block, String oldName, String newName) {
313        for (Warrant w : getNamedBeanSet()) {
314            List<BlockOrder> orders = w.getBlockOrders();
315            Iterator<BlockOrder> it = orders.iterator();
316            while (it.hasNext()) {
317                BlockOrder bo = it.next();
318                if (bo.getBlock().equals(block) && bo.getPathName().equals(oldName)) {
319                    bo.setPathName(newName);
320                }
321            }
322        }
323    }
324
325    /**
326     * Get the default WarrantManager.
327     *
328     * @return the default WarrantManager, creating it if necessary
329     */
330    public static WarrantManager getDefault() {
331        return InstanceManager.getOptionalDefault(WarrantManager.class).orElseGet(() -> {
332            return InstanceManager.setDefault(WarrantManager.class, new WarrantManager());
333        });
334    }
335
336    @Override
337    @Nonnull
338    public String getBeanTypeHandled(boolean plural) {
339        return Bundle.getMessage(plural ? "BeanNameWarrants" : "BeanNameWarrant");
340    }
341
342    /**
343     * {@inheritDoc}
344     */
345    @Override
346    public Class<Warrant> getNamedBeanClass() {
347        return Warrant.class;
348    }
349
350    protected void setMergeProfile(String id, RosterSpeedProfile merge) {
351        if (_shutDownTask == null) {
352            if (!WarrantPreferences.getDefault().getShutdown().equals((WarrantPreferences.Shutdown.NO_MERGE))) {
353                _shutDownTask = new WarrantShutdownTask("WarrantRosterSpeedProfileCheck");
354                jmri.InstanceManager.getDefault(jmri.ShutDownManager.class).register(_shutDownTask);
355            }
356        }
357        log.debug("setMergeProfile id = {}", id);
358        if (id != null && merge != null) {
359            _mergeProfiles.remove(id);
360            _mergeProfiles.put(id, merge);
361        }
362    }
363
364    /**
365     * Return a copy of the RosterSpeedProfile for Roster entry
366     * @param id roster id
367     * @return RosterSpeedProfile
368     */
369    protected RosterSpeedProfile getMergeProfile(String id) {
370        log.debug("getMergeProfile id = {}", id);
371        return _mergeProfiles.get(id);
372    }
373
374    protected RosterSpeedProfile makeProfileCopy(RosterSpeedProfile mergeProfile, @Nonnull RosterEntry re) {
375        RosterSpeedProfile profile = new RosterSpeedProfile(re);
376        if (mergeProfile == null) {
377            mergeProfile = re.getSpeedProfile();
378            if (mergeProfile == null) {
379                mergeProfile = new RosterSpeedProfile(re);
380                re.setSpeedProfile(mergeProfile);
381            }
382        }
383        // make copy of mergeProfile
384        TreeMap<Integer, SpeedStep> rosterTree = mergeProfile.getProfileSpeeds();
385        for (Map.Entry<Integer, SpeedStep> entry : rosterTree.entrySet()) {
386            profile.setSpeed(entry.getKey(), entry.getValue().getForwardSpeed(), entry.getValue().getReverseSpeed());
387        }
388        return profile;
389    }
390
391    protected HashMap<String, RosterSpeedProfile> getMergeProfiles() {
392        return _mergeProfiles;
393    }
394
395    @Override
396    public void dispose(){
397        for(Warrant w:_beans){
398            w.dispose();
399        }
400        super.dispose();
401    }
402
403    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(WarrantManager.class);
404
405}