001package jmri.jmrit.operations.rollingstock.engines;
002
003import java.beans.PropertyChangeEvent;
004import java.util.*;
005
006import javax.swing.JComboBox;
007
008import org.jdom2.Element;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012import jmri.*;
013import jmri.jmrit.operations.OperationsPanel;
014import jmri.jmrit.operations.locations.Track;
015import jmri.jmrit.operations.rollingstock.RollingStock;
016import jmri.jmrit.operations.rollingstock.RollingStockManager;
017import jmri.jmrit.operations.setup.OperationsSetupXml;
018import jmri.jmrit.operations.trains.Train;
019import jmri.jmrit.operations.trains.TrainManifestHeaderText;
020
021/**
022 * Manages the engines.
023 *
024 * @author Daniel Boudreau Copyright (C) 2008
025 */
026public class EngineManager extends RollingStockManager<Engine>
027        implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize {
028
029    public EngineManager() {
030    }
031
032    /**
033     * Finds an existing engine or creates a new engine if needed requires engine's
034     * road and number
035     *
036     * @param engineRoad   The engine's road initials
037     * @param engineNumber The engine's road number
038     *
039     * @return new engine or existing engine
040     */
041    @Override
042    public Engine newRS(String engineRoad, String engineNumber) {
043        Engine engine = getByRoadAndNumber(engineRoad, engineNumber);
044        if (engine == null) {
045            engine = new Engine(engineRoad, engineNumber);
046            register(engine);
047        }
048        return engine;
049    }
050
051    @Override
052    public void deregister(Engine engine) {
053        super.deregister(engine);
054        InstanceManager.getDefault(EngineManagerXml.class).setDirty(true);
055    }
056
057    /**
058     * Sort by engine model
059     *
060     * @return list of engines ordered by engine model
061     */
062    public List<Engine> getByModelList() {
063        return getByList(getByRoadNameList(), BY_MODEL);
064    }
065
066    /**
067     * Sort by engine consist
068     *
069     * @return list of engines ordered by engine consist
070     */
071    public List<Engine> getByConsistList() {
072        return getByList(getByRoadNameList(), BY_CONSIST);
073    }
074
075    public List<Engine> getByHpList() {
076        return getByList(getByModelList(), BY_HP);
077    }
078
079    // The special sort options for engines
080    private static final int BY_MODEL = 30;
081    private static final int BY_CONSIST = 31;
082    private static final int BY_HP = 32;
083
084    // add engine options to sort comparator
085    @Override
086    protected java.util.Comparator<Engine> getComparator(int attribute) {
087        switch (attribute) {
088            case BY_MODEL:
089                return (e1, e2) -> (e1.getModel().compareToIgnoreCase(e2.getModel()));
090            case BY_CONSIST:
091                return (e1, e2) -> (e1.getConsistName().compareToIgnoreCase(e2.getConsistName()));
092            case BY_HP:
093                return (e1, e2) -> (e1.getHpInteger() - e2.getHpInteger());
094            default:
095                return super.getComparator(attribute);
096        }
097    }
098
099    /**
100     * Returns a list of available engines (no assigned train). Engines are
101     * ordered by track priority and least recently moved to most recently
102     * moved.
103     *
104     * @param train The Train requesting this list.
105     * @return Ordered list of engines not assigned to a train
106     */
107    public List<Engine> getAvailableTrainList(Train train) {
108        // now build list of available engines for this route
109        List<Engine> out = new ArrayList<>();
110        // get engines by track priority and moves
111        List<Engine> sortByPriority = sortByTrackPriority(getByMovesList());
112        for (Engine engine : sortByPriority) {
113            if (engine.getTrack() != null && (engine.getTrain() == null || engine.getTrain() == train)) {
114                out.add(engine);
115            }
116        }
117        return out;
118    }
119
120    /**
121     * Returns a list of locos sorted by blocking number for a train. This returns a
122     * list of consisted locos in the order that they were entered in.
123     *
124     * @param train The Train requesting this list.
125     * @return A list of sorted locos.
126     */
127    public List<Engine> getByTrainBlockingList(Train train) {
128        return getByList(super.getByTrainList(train), BY_BLOCKING);
129    }
130
131    /**
132     * Get a list of engine road names.
133     *
134     * @param model The string model name, can be NONE.
135     *
136     * @return List of engine road names.
137     */
138    public List<String> getEngineRoadNames(String model) {
139        List<String> names = new ArrayList<>();
140        Enumeration<String> en = _hashTable.keys();
141        while (en.hasMoreElements()) {
142            Engine engine = getById(en.nextElement());
143            if ((engine.getModel().equals(model) || model.equals(NONE)) && !names.contains(engine.getRoadName())) {
144                names.add(engine.getRoadName());
145            }
146        }
147        java.util.Collections.sort(names);
148        return names;
149    }
150
151    public void updateEngineRoadComboBox(String engineModel, JComboBox<String> roadEngineBox) {
152        roadEngineBox.removeAllItems();
153        roadEngineBox.addItem(NONE);
154        List<String> roads = getEngineRoadNames(engineModel);
155        for (String roadName : roads) {
156            roadEngineBox.addItem(roadName);
157        }
158        OperationsPanel.padComboBox(roadEngineBox);
159    }
160
161    int _commentLength = 0;
162
163    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST",
164            justification="I18N of Info Message")
165    public int getMaxCommentLength() {
166        if (_commentLength == 0) {
167            _commentLength = TrainManifestHeaderText.getStringHeader_Comment().length();
168            String comment = "";
169            Engine engineMax = null;
170            for (Engine engine : getList()) {
171                if (engine.getComment().length() > _commentLength) {
172                    _commentLength = engine.getComment().length();
173                    comment = engine.getComment();
174                    engineMax = engine;
175                }
176            }
177            if (engineMax != null) {
178                log.info(Bundle.getMessage("InfoMaxComment", engineMax.toString(), comment, _commentLength));
179            }
180        }
181        return _commentLength;
182    }
183    
184    /**
185     * Creates a clone for the engine, and clones if the engine is part of a consist.
186     * Note that a engine have have multiple clones.
187     * 
188     * @param engine       The engine to clone
189     * @param track     The destination track for the clones
190     * @param train     The train transporting the clones
191     * @param startTime The date and time the clones were moved
192     * @return clone for this engine
193     */
194    public Engine createClone(Engine engine, Track track, Train train, Date startTime) {
195        int cloneCreationOrder = getCloneCreationOrder();
196        Engine cloneEng = engine.copy();
197        cloneEng.setNumber(engine.getNumber() + Engine.CLONE + cloneCreationOrder);
198        cloneEng.setClone(true);
199        // register engine before setting location so the engine gets logged
200        register(cloneEng);
201        cloneEng.setLocation(engine.getLocation(), engine.getTrack(), RollingStock.FORCE);
202        // for reset
203        cloneEng.setLastRouteId(engine.getLastRouteId());
204        cloneEng.setMoves(engine.getMoves());
205        if (engine.getConsist() != null) {
206            String consistName = engine.getConsistName() + Engine.CLONE + cloneCreationOrder;
207            Consist consist = InstanceManager.getDefault(ConsistManager.class).newConsist(consistName);
208            cloneEng.setConsist(consist);
209            for (Engine e : engine.getConsist().getEngines()) {
210                if (e != engine) {
211                    Engine nEng = e.copy();
212                    nEng.setNumber(e.getNumber() + Engine.CLONE + cloneCreationOrder);
213                    nEng.setClone(true);
214                    nEng.setConsist(consist);
215                    nEng.setMoves(e.getMoves());
216                    register(nEng);
217                    nEng.setLocation(engine.getLocation(), engine.getTrack(), RollingStock.FORCE);
218                    // for reset
219                    // move engine to new location for later pick up
220                    e.setLocation(track.getLocation(), track, RollingStock.FORCE);
221                    e.setLastTrain(train);
222                    e.setLastLocationId(engine.getLocationId());
223                    e.setLastTrackId(engine.getTrackId());
224                    e.setLastDate(startTime);
225                    e.setMoves(e.getMoves() + 1); // bump count
226                    e.setCloneOrder(cloneCreationOrder); // for reset
227                }
228            }
229        }
230        // move engine to new location for later pick up
231        engine.setLocation(track.getLocation(), track, RollingStock.FORCE);
232        engine.setLastTrain(train);
233        engine.setLastLocationId(cloneEng.getLocationId());
234        engine.setLastTrackId(cloneEng.getTrackId());
235        engine.setLastRouteId(train.getRoute().getId());
236        // this engine was moved during the build process
237        engine.setLastDate(startTime);
238        engine.setMoves(engine.getMoves() + 1); // bump count
239        engine.setCloneOrder(cloneCreationOrder); // for reset
240        engine.setDestination(null, null);    
241        return cloneEng;
242    }
243
244    public void load(Element root) {
245        if (root.getChild(Xml.ENGINES) != null) {
246            List<Element> engines = root.getChild(Xml.ENGINES).getChildren(Xml.ENGINE);
247            log.debug("readFile sees {} engines", engines.size());
248            for (Element e : engines) {
249                register(new Engine(e));
250            }
251        }
252    }
253
254    /**
255     * Create an XML element to represent this Entry. This member has to remain
256     * synchronized with the detailed DTD in operations-engines.dtd.
257     *
258     * @param root The common Element for operations-engines.dtd.
259     *
260     */
261    public void store(Element root) {
262        Element values;
263        root.addContent(values = new Element(Xml.ENGINES));
264        // add entries
265        for (RollingStock rs : getByRoadNameList()) {
266            Engine eng = (Engine) rs;
267            values.addContent(eng.store());
268        }
269    }
270
271    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
272        // Set dirty
273        InstanceManager.getDefault(EngineManagerXml.class).setDirty(true);
274        super.firePropertyChange(p, old, n);
275    }
276
277    @Override
278    public void propertyChange(PropertyChangeEvent evt) {
279        if (evt.getPropertyName().equals(Engine.COMMENT_CHANGED_PROPERTY)) {
280            _commentLength = 0;
281        }
282        super.propertyChange(evt);
283    }
284
285    private final static Logger log = LoggerFactory.getLogger(EngineManager.class);
286
287    @Override
288    public void initialize() {
289        InstanceManager.getDefault(OperationsSetupXml.class); // load setup
290        // create manager to load engines and their attributes
291        InstanceManager.getDefault(EngineManagerXml.class);
292    }
293}