001package jmri.jmrit.logix;
002
003import javax.annotation.CheckForNull;
004import javax.annotation.Nonnull;
005
006import jmri.BeanSetting;
007import jmri.jmrit.logix.TrainOrder.Cause;
008
009/**
010 * A BlockOrder is a row in the route of the warrant.
011 * It contains where the warranted train enters a block, the path it takes and
012 * where it exits the block.
013 * (The route is a list of BlockOrder.)
014 * The Engineer is notified when the train enters the block.
015 *
016 * @author Pete Cressman Copyright (C) 2009
017 */
018public class BlockOrder {
019
020    private int _index;
021    private OBlock _block;     // OBlock of these orders
022    private String _pathName;  // path the train is to take in the block
023    private String _entryName; // Name of entry Portal
024    private String _exitName;  // Name of exit Portal
025    private float _pathLength; // path length in millimeters
026
027    public BlockOrder(@Nonnull OBlock block) {
028        _block = block;
029    }
030
031    /**
032     * Create BlockOrder.
033     *
034     * @param block OBlock of this order
035     * @param path  MUST be a path in the blocK
036     * @param entry MUST be a name of a Portal to the path
037     * @param exit  MUST be a name of a Portal to the path
038     */
039    public BlockOrder(@Nonnull OBlock block, String path, String entry, String exit) {
040        this(block);
041        _pathName = path;
042        _entryName = entry;
043        _exitName = exit;
044    }
045
046    // for use by WarrantTableFrame 
047    protected BlockOrder(@Nonnull BlockOrder bo) {
048        _index = bo.getIndex();
049        _block = bo.getBlock();
050        _pathName = bo.getPathName();
051        _entryName = bo.getEntryName();
052        _exitName = bo.getExitName();
053    }
054
055    public void setIndex(int idx) {
056        _index = idx;
057    }
058
059    public int getIndex() {
060        return _index;
061    }
062
063    protected void setEntryName(String name) {
064        _entryName = name;
065    }
066
067    public String getEntryName() {
068        return _entryName;
069    }
070
071    protected void setExitName(String name) {
072        _exitName = name;
073    }
074
075    public String getExitName() {
076        return _exitName;
077    }
078
079    /**
080     * Set Path. Note that the Path's 'fromPortal' and 'toPortal' have no
081     * bearing on the BlockOrder's entryPortal and exitPortal.
082     * @param path  Name of the OPath connecting the entry and exit Portals
083     */
084    protected void setPathName(String path) {
085        _pathName = path;
086    }
087
088    public String getPathName() {
089        return _pathName;
090    }
091
092    protected OPath getPath() {
093        return _block.getPathByName(_pathName);
094    }
095
096    protected String setPath(Warrant warrant) {
097        String msg = _block.setPath(getPathName(), warrant);
098        if (msg == null) {
099            Portal p = getEntryPortal();
100            if (p != null) {
101                p.setEntryState(_block);
102            }
103        }
104        return msg;
105    }
106
107    @Nonnull
108    protected TrainOrder allocatePaths(@Nonnull Warrant warrant, boolean allocate) {
109        if (_pathName == null) {
110            log.error("setPaths({}) - {}", warrant.getDisplayName(),
111                Bundle.getMessage("NoPaths", _block.getDisplayName()));
112            return new TrainOrder(Warrant.Stop, Cause.ERROR, _index, _index, 
113                    Bundle.getMessage("NoPaths", _block.getDisplayName()));
114        }
115        log.debug("{}: calls allocatePaths() in block \"{}\" for path \"{}\". _index={}",
116                warrant.getDisplayName(), _block.getDisplayName(), _pathName, _index);
117        TrainOrder to = findStopCondition(this, warrant);
118        if (to != null && Warrant.Stop.equals(to._speedType)) {
119            return to;
120        }
121        String msg = _block.allocate(warrant);
122        if (msg != null) {  // unnecessary, findStopCondition() already has checked
123            return new TrainOrder(Warrant.Stop, Cause.ERROR, _index, _index, msg);
124        }
125
126        // Check if next block can be allocated
127        BlockOrder bo1 = warrant.getBlockOrderAt(_index + 1);
128        if (bo1 != null) {
129            OBlock nextBlock = bo1.getBlock();
130            TrainOrder to1 = findStopCondition(bo1, warrant);
131            if (to1 == null || !Warrant.Stop.equals(to1._speedType)) { // Train may enter block of bo1
132                if (allocate) {
133                    nextBlock.allocate(warrant);
134                    nextBlock.showAllocated(warrant, bo1.getPathName());
135                }
136            } else {
137                // See if path to exit can be set without messing up block of bo1
138                OPath path1 = getPath();
139                Portal exit = getExitPortal();
140                msg =  pathsConnect(path1, exit, bo1.getBlock());
141            }
142            if (msg != null) {
143                // cannot set path
144                return new TrainOrder(Warrant.Stop, ( to1 != null ? to1._cause : Cause.WARRANT), 
145                    bo1.getIndex(), _index, msg);
146            }
147            // Crossovers typically have both switches controlled by one TO, 
148            // yet each switch is in a different block. Setting the path may change
149            // a shared TO for another warrant and change its path to
150            // short or derail its train entering the block. So we may not allow
151            // this warrant to set the path in bo1.  
152            // Because the path in bo1 cannot be set, it is not safe to enter
153            // the next block. The warrant must hold the train in this block.
154            // However, the path in this block may be set
155        }
156        if (allocate) {
157            msg = setPath(warrant);
158            if (msg != null) {  // unnecessary, already been checked
159                return new TrainOrder(Warrant.Stop, Cause.ERROR, _index, _index, msg);
160            }
161        }
162        if (to != null) {
163            return to;
164        }
165        return new TrainOrder(null, Cause.NONE, _index, _index, null);
166    }
167
168    @CheckForNull
169    private TrainOrder findStopCondition(@Nonnull BlockOrder bo, @Nonnull Warrant warrant) {
170        OBlock block = bo.getBlock();
171        Warrant w = block.getWarrant();
172        if (w != null && !warrant.equals(w)) {
173           return new TrainOrder(Warrant.Stop, Cause.WARRANT, bo.getIndex(), bo.getIndex(),
174                   Bundle.getMessage("AllocatedToWarrant",
175                   w.getDisplayName(), block.getDisplayName(), w.getTrainName()));
176        }
177        if (block.isOccupied()) {
178            String rogue = (String)block.getValue();
179            if (rogue == null) {
180                rogue = Bundle.getMessage("unknownTrain");
181            }
182            if (!rogue.equals(warrant.getTrainName())) {
183                return new TrainOrder(Warrant.Stop, Cause.OCCUPY, bo.getIndex(), bo.getIndex(),
184                        Bundle.getMessage("blockInUse", rogue, block.getDisplayName()));
185            }
186        }
187        String speedType = getPermissibleSpeedAt(bo);
188        if (speedType != null) {
189            String msg;
190            if (Warrant.Stop.equals(speedType)) {
191                msg = Bundle.getMessage("BlockStopAspect", block.getDisplayName(), speedType);
192            } else {
193                msg = Bundle.getMessage("BlockSpeedAspect", block.getDisplayName(), speedType);
194            }
195            return new TrainOrder(speedType, Cause.SIGNAL, bo.getIndex(), bo.getIndex(), msg);
196        }
197        return null;
198    }
199
200    @CheckForNull
201    protected String pathsConnect(@Nonnull OPath path1, @CheckForNull Portal exit, @CheckForNull OBlock block) {
202        if (exit == null || block == null) {
203            return null;
204        }
205        
206        OPath path2 = block.getPath();
207        if (path2 == null) {
208            return null;
209        }
210        for (BeanSetting bs1 : path1.getSettings()) {
211            for (BeanSetting bs2 : path2.getSettings()) {
212                if (bs1.getBean().equals(bs2.getBean())) {
213                    // TO is shared (same bean)
214                    if (log.isDebugEnabled()) {
215                        if (bs1.equals(bs2)) {
216                            log.debug("Path \"{}\" in block \"{}\" and \"{}\" in block \"{}\" agree on setting of shared turnout \"{}\"",
217                                    path1.getName(), _block.getDisplayName(), path2.getName(), block.getDisplayName(), 
218                                    bs1.getBean().getDisplayName());
219                        } else {
220                            log.debug("Path \"{}\" in block \"{}\" and \"{}\" in block \"{}\" have opposed settings of shared turnout \"{}\"",
221                                    path1.getName(), _block.getDisplayName(), path2.getName(), block.getDisplayName(), 
222                                    bs1.getBean().getDisplayName());
223                        }
224                    }
225                    return  Bundle.getMessage("SharedTurnout", bs1.getBean().getDisplayName(), _block.getDisplayName(), block.getDisplayName());
226                }
227            }
228        }
229        return null;
230    }
231
232    protected static String getPermissibleSpeedAt(BlockOrder bo) {
233        String speedType = bo.getPermissibleEntranceSpeed();
234        if (speedType != null) {
235            log.debug("getPermissibleSpeedAt(): \"{}\" Signal speed= {}",
236                bo.getBlock().getDisplayName(), speedType);
237        } else { //  if signal is configured, ignore block
238            speedType = bo.getBlock().getBlockSpeed();
239            if (speedType.equals("")) {
240                speedType = null;
241            }
242            if (speedType != null) {
243                log.debug("getPermissibleSpeedAt(): \"{}\" Block speed= {}",
244                    bo.getBlock().getDisplayName(), speedType);
245            }
246        }
247        return speedType;
248    }
249
250    protected void setPathLength(float len) {
251        _pathLength = len;
252    }
253
254    protected float getPathLength() {
255        if (_pathLength <= 0) {
256            OPath p  = getPath();
257            if (p != null) {
258                _pathLength = p.getLengthMm();
259            } else {
260                _pathLength = 0;
261            }
262        }
263        return _pathLength;
264    }
265
266    protected void setBlock(@Nonnull OBlock block) {
267        _block = block;
268    }
269
270    @Nonnull
271    public OBlock getBlock() {
272        return _block;
273    }
274
275    @CheckForNull
276    protected Portal getEntryPortal() {
277        if (_entryName == null) {
278            return null;
279        }
280        return _block.getPortalByName(_entryName);
281    }
282
283    @CheckForNull
284    protected Portal getExitPortal() {
285        if (_exitName == null) {
286            return null;
287        }
288        return _block.getPortalByName(_exitName);
289    }
290
291    /**
292     * Check signals for entrance into next block.
293     *
294     * @return speed
295     */
296    protected String getPermissibleEntranceSpeed() {
297        Portal portal = _block.getPortalByName(getEntryName());
298        if (portal != null) {
299            return portal.getPermissibleSpeed(_block, true);
300        }
301        return null;
302    }
303
304    protected float getEntranceSpace() {
305        Portal portal = _block.getPortalByName(getEntryName());
306        if (portal != null) {
307            return portal.getEntranceSpaceForBlock(_block);
308        }
309        return 0;
310    }
311
312    /**
313     * Get the signal protecting entry into the block of this BlockOrder.
314     * @return signal
315     */
316    @CheckForNull
317    protected jmri.NamedBean getSignal() {
318        Portal portal = getEntryPortal();
319        if (portal != null) {            
320            return portal.getSignalProtectingBlock(_block);
321        }
322        return null;
323    }
324
325    @Override
326    public String toString() {
327        StringBuilder sb = new StringBuilder("BlockOrder: Block \"");
328        sb.append( _block.getDisplayName());
329        sb.append("\" has Path \"");
330        sb.append(_pathName);
331        sb.append("\" with Portals, entry= \"");
332        sb.append(_entryName);
333        sb.append("\" and exit= \"");
334        sb.append(_exitName);
335        sb.append("\"");
336        return sb.toString();
337    }
338
339    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BlockOrder.class);
340
341}