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