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