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