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