001package jmri.jmrit.operations.locations.schedules;
002
003import java.beans.PropertyChangeListener;
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.beans.PropertyChangeSupport;
014import jmri.jmrit.operations.OperationsPanel;
015import jmri.jmrit.operations.locations.*;
016import jmri.jmrit.operations.rollingstock.cars.CarRoads;
017import jmri.jmrit.operations.rollingstock.cars.CarTypes;
018import jmri.jmrit.operations.setup.Control;
019
020/**
021 * Manages schedules.
022 *
023 * @author Bob Jacobsen Copyright (C) 2003
024 * @author Daniel Boudreau Copyright (C) 2008, 2013
025 */
026public class ScheduleManager extends PropertyChangeSupport implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize, PropertyChangeListener {
027
028    public static final String LISTLENGTH_CHANGED_PROPERTY = "scheduleListLength"; // NOI18N
029
030    public ScheduleManager() {
031    }
032
033    private int _id = 0;
034
035    public void dispose() {
036        _scheduleHashTable.clear();
037    }
038
039    // stores known Schedule instances by id
040    protected Hashtable<String, Schedule> _scheduleHashTable = new Hashtable<String, Schedule>();
041
042    /**
043     * @return Number of schedules
044     */
045    public int numEntries() {
046        return _scheduleHashTable.size();
047    }
048
049    /**
050     * @param name The string name for the schedule
051     * @return requested Schedule object or null if none exists
052     */
053    public Schedule getScheduleByName(String name) {
054        Schedule s;
055        Enumeration<Schedule> en = _scheduleHashTable.elements();
056        while (en.hasMoreElements()) {
057            s = en.nextElement();
058            if (s.getName().equals(name)) {
059                return s;
060            }
061        }
062        return null;
063    }
064
065    public Schedule getScheduleById(String id) {
066        return _scheduleHashTable.get(id);
067    }
068
069    /**
070     * Finds an existing schedule or creates a new schedule if needed requires
071     * schedule's name creates a unique id for this schedule
072     *
073     * @param name The string name for this schedule
074     *
075     *
076     * @return new schedule or existing schedule
077     */
078    public Schedule newSchedule(String name) {
079        Schedule schedule = getScheduleByName(name);
080        if (schedule == null && !name.isBlank()) {
081            _id++;
082            schedule = new Schedule(Integer.toString(_id), name);
083            Integer oldSize = Integer.valueOf(_scheduleHashTable.size());
084            _scheduleHashTable.put(schedule.getId(), schedule);
085            setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(_scheduleHashTable
086                    .size()));
087        }
088        return schedule;
089    }
090
091    /**
092     * Remember a NamedBean Object created outside the manager.
093     *
094     * @param schedule The Schedule to add.
095     */
096    public void register(Schedule schedule) {
097        Integer oldSize = Integer.valueOf(_scheduleHashTable.size());
098        _scheduleHashTable.put(schedule.getId(), schedule);
099        // find last id created
100        int id = Integer.parseInt(schedule.getId());
101        if (id > _id) {
102            _id = id;
103        }
104        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(_scheduleHashTable.size()));
105    }
106
107    /**
108     * Forget a NamedBean Object created outside the manager.
109     *
110     * @param schedule The Schedule to delete.
111     */
112    public void deregister(Schedule schedule) {
113        if (schedule == null) {
114            return;
115        }
116        schedule.dispose();
117        Integer oldSize = Integer.valueOf(_scheduleHashTable.size());
118        _scheduleHashTable.remove(schedule.getId());
119        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(_scheduleHashTable.size()));
120    }
121
122    /**
123     * Sort by schedule name
124     *
125     * @return list of schedules ordered by name
126     */
127    public List<Schedule> getSchedulesByNameList() {
128        List<Schedule> sortList = getList();
129        // now re-sort
130        List<Schedule> out = new ArrayList<Schedule>();
131        for (Schedule sch : sortList) {
132            for (int j = 0; j < out.size(); j++) {
133                if (sch.getName().compareToIgnoreCase(out.get(j).getName()) < 0) {
134                    out.add(j, sch);
135                    break;
136                }
137            }
138            if (!out.contains(sch)) {
139                out.add(sch);
140            }
141        }
142        return out;
143
144    }
145
146    /**
147     * Sort by schedule id number
148     *
149     * @return list of schedules ordered by id number
150     */
151    public List<Schedule> getSchedulesByIdList() {
152        List<Schedule> sortList = getList();
153        // now re-sort
154        List<Schedule> out = new ArrayList<Schedule>();
155        for (Schedule sch : sortList) {
156            for (int j = 0; j < out.size(); j++) {
157                try {
158                    if (Integer.parseInt(sch.getId()) < Integer.parseInt(out.get(j).getId())) {
159                        out.add(j, sch);
160                        break;
161                    }
162                } catch (NumberFormatException e) {
163                    log.debug("list id number isn't a number");
164                }
165            }
166            if (!out.contains(sch)) {
167                out.add(sch);
168            }
169        }
170        return out;
171    }
172
173    private List<Schedule> getList() {
174        List<Schedule> out = new ArrayList<Schedule>();
175        Enumeration<Schedule> en = _scheduleHashTable.elements();
176        while (en.hasMoreElements()) {
177            out.add(en.nextElement());
178        }
179        return out;
180    }
181
182    public Schedule copySchedule(Schedule schedule, String newScheduleName) {
183        Schedule newSchedule = newSchedule(newScheduleName);
184        for (ScheduleItem si : schedule.getItemsBySequenceList()) {
185            ScheduleItem newSi = newSchedule.addItem(si.getTypeName());
186            newSi.copyScheduleItem(si);
187        }
188        return newSchedule;
189    }
190
191    public void resetHitCounts() {
192        for (Schedule schedule : getList()) {
193            schedule.resetHitCounts();
194        }
195    }
196
197    /**
198     * Gets a JComboBox loaded with schedules.
199     *
200     * @return JComboBox with a list of schedules.
201     */
202    public JComboBox<Schedule> getComboBox() {
203        JComboBox<Schedule> box = new JComboBox<>();
204        OperationsPanel.padComboBox(box, Control.max_len_string_location_name);
205        updateComboBox(box);
206        return box;
207    }
208
209    /**
210     * Update a JComboBox with the latest schedules.
211     *
212     * @param box the JComboBox needing an update.
213     */
214    public void updateComboBox(JComboBox<Schedule> box) {
215        box.removeAllItems();
216        box.addItem(null);
217        for (Schedule schedule : getSchedulesByNameList()) {
218            box.addItem(schedule);
219        }
220    }
221
222    /**
223     * Replaces car type in all schedules.
224     *
225     * @param oldType car type to be replaced.
226     * @param newType replacement car type.
227     */
228    public void replaceType(String oldType, String newType) {
229        for (Schedule sch : getSchedulesByIdList()) {
230            for (ScheduleItem si : sch.getItemsBySequenceList()) {
231                if (si.getTypeName().equals(oldType)) {
232                    si.setTypeName(newType);
233                }
234            }
235        }
236    }
237
238    /**
239     * Replaces car roads in all schedules.
240     *
241     * @param oldRoad car road to be replaced.
242     * @param newRoad replacement car road.
243     */
244    public void replaceRoad(String oldRoad, String newRoad) {
245        if (newRoad == null) {
246            return;
247        }
248        for (Schedule sch : getSchedulesByIdList()) {
249            for (ScheduleItem si : sch.getItemsBySequenceList()) {
250                if (si.getRoadName().equals(oldRoad)) {
251                    si.setRoadName(newRoad);
252                }
253            }
254        }
255    }
256
257    /**
258     * Replaces car loads in all schedules with specific car type.
259     *
260     * @param type    car type.
261     * @param oldLoad car load to be replaced.
262     * @param newLoad replacement car load.
263     */
264    public void replaceLoad(String type, String oldLoad, String newLoad) {
265        for (Schedule sch : getSchedulesByIdList()) {
266            for (ScheduleItem si : sch.getItemsBySequenceList()) {
267                if (si.getTypeName().equals(type) && si.getReceiveLoadName().equals(oldLoad)) {
268                    if (newLoad != null) {
269                        si.setReceiveLoadName(newLoad);
270                    } else {
271                        si.setReceiveLoadName(ScheduleItem.NONE);
272                    }
273                }
274                if (si.getTypeName().equals(type) && si.getShipLoadName().equals(oldLoad)) {
275                    if (newLoad != null) {
276                        si.setShipLoadName(newLoad);
277                    } else {
278                        si.setShipLoadName(ScheduleItem.NONE);
279                    }
280                }
281            }
282        }
283    }
284
285    public void replaceTrack(Track oldTrack, Track newTrack) {
286        for (Schedule sch : getSchedulesByIdList()) {
287            for (ScheduleItem si : sch.getItemsBySequenceList()) {
288                if (si.getDestinationTrack() == oldTrack) {
289                    si.setDestination(newTrack.getLocation());
290                    si.setDestinationTrack(newTrack);
291                }
292            }
293        }
294    }
295
296    /**
297     * Gets a JComboBox with a list of spurs that use this schedule.
298     *
299     * @param schedule The schedule for this JComboBox.
300     * @return JComboBox with a list of spurs using schedule.
301     */
302    public JComboBox<LocationTrackPair> getSpursByScheduleComboBox(Schedule schedule) {
303        JComboBox<LocationTrackPair> box = new JComboBox<>();
304        // search all spurs for that use schedule
305        for (Track spur : getListSpurs(schedule)) {
306            if (spur.getScheduleId().equals(schedule.getId())) {
307                LocationTrackPair ltp = new LocationTrackPair(spur);
308                box.addItem(ltp);
309            }
310        }
311        return box;
312    }
313
314    public List<Track> getListSpurs(Schedule schedule) {
315        List<Track> spurs = new ArrayList<Track>();
316        for (Location location : InstanceManager.getDefault(LocationManager.class).getLocationsByNameList()) {
317            for (Track spur : location.getTracksByNameList(Track.SPUR)) {
318                if (spur.getScheduleId().equals(schedule.getId())) {
319                    spurs.add(spur);
320                }
321            }
322        }
323        return spurs;
324    }
325
326    public void load(Element root) {
327        if (root.getChild(Xml.SCHEDULES) != null) {
328            List<Element> eSchedules = root.getChild(Xml.SCHEDULES).getChildren(Xml.SCHEDULE);
329            log.debug("readFile sees {} schedules", eSchedules.size());
330            for (Element eSchedule : eSchedules) {
331                register(new Schedule(eSchedule));
332            }
333        }
334    }
335
336    public void store(Element root) {
337        Element values;
338        root.addContent(values = new Element(Xml.SCHEDULES));
339        // add entries
340        for (Schedule schedule : getSchedulesByIdList()) {
341            values.addContent(schedule.store());
342        }
343    }
344
345    /**
346     * Check for car type and road name changes.
347     */
348    @Override
349    public void propertyChange(java.beans.PropertyChangeEvent e) {
350        if (Control.SHOW_PROPERTY) {
351            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e
352                    .getNewValue());
353        }
354        if (e.getPropertyName().equals(CarTypes.CARTYPES_NAME_CHANGED_PROPERTY)) {
355            replaceType((String) e.getOldValue(), (String) e.getNewValue());
356        }
357        if (e.getPropertyName().equals(CarRoads.CARROADS_NAME_CHANGED_PROPERTY)) {
358            replaceRoad((String) e.getOldValue(), (String) e.getNewValue());
359        }
360    }
361
362    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
363        // set dirty
364        InstanceManager.getDefault(LocationManagerXml.class).setDirty(true);
365        firePropertyChange(p, old, n);
366    }
367
368    private final static Logger log = LoggerFactory.getLogger(ScheduleManager.class);
369
370    @Override
371    public void initialize() {
372        InstanceManager.getDefault(CarTypes.class).addPropertyChangeListener(this);
373        InstanceManager.getDefault(CarRoads.class).addPropertyChangeListener(this);
374    }
375
376}