001package jmri.jmrit.operations.trains.schedules;
002
003import java.beans.PropertyChangeListener;
004import java.util.*;
005
006import javax.swing.JComboBox;
007
008import org.jdom2.Attribute;
009import org.jdom2.Element;
010import org.slf4j.Logger;
011import org.slf4j.LoggerFactory;
012
013import jmri.*;
014import jmri.beans.PropertyChangeSupport;
015import jmri.jmrit.operations.locations.Location;
016import jmri.jmrit.operations.locations.LocationManager;
017import jmri.jmrit.operations.setup.Setup;
018import jmri.jmrit.operations.trains.*;
019
020/**
021 * Manages train schedules. The default is the days of the week, but can be
022 * anything the user wants when defining when trains will run.
023 *
024 * @author Bob Jacobsen Copyright (C) 2003
025 * @author Daniel Boudreau Copyright (C) 2010
026 */
027public class TrainScheduleManager extends PropertyChangeSupport implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize, PropertyChangeListener {
028
029    public TrainScheduleManager() {
030    }
031
032    public static final String NONE = "";
033    private String _trainScheduleActiveId = NONE;
034    private int _id = 0;
035    
036    public static final String LISTLENGTH_CHANGED_PROPERTY = "trainScheduleListLength"; // NOI18N
037    public static final String SCHEDULE_ID_CHANGED_PROPERTY = "ActiveTrainScheduleId"; // NOI18N
038
039    public void dispose() {
040        _scheduleHashTable.clear();
041    }
042
043    // stores known TrainSchedule instances by id
044    protected Hashtable<String, TrainSchedule> _scheduleHashTable = new Hashtable<>();
045
046    /**
047     * @return Number of schedules
048     */
049    public int numEntries() {
050        return _scheduleHashTable.size();
051    }
052    
053    /**
054     * Sets the selected schedule id
055     *
056     * @param id Selected schedule id
057     */
058    public void setTrainScheduleActiveId(String id) {
059        String old = _trainScheduleActiveId;
060        _trainScheduleActiveId = id;
061        if (!old.equals(id)) {
062            setDirtyAndFirePropertyChange(SCHEDULE_ID_CHANGED_PROPERTY, old, id);
063        }
064    }
065
066    public String getTrainScheduleActiveId() {
067        return _trainScheduleActiveId;
068    }
069    
070    public TrainSchedule getActiveSchedule() {
071        return getScheduleById(getTrainScheduleActiveId());
072    }
073
074    /**
075     * @param name The schedule string name to search for.
076     * @return requested TrainSchedule object or null if none exists
077     */
078    public TrainSchedule getScheduleByName(String name) {
079        TrainSchedule s;
080        Enumeration<TrainSchedule> en = _scheduleHashTable.elements();
081        while (en.hasMoreElements()) {
082            s = en.nextElement();
083            if (s.getName().equals(name)) {
084                return s;
085            }
086        }
087        return null;
088    }
089
090    public TrainSchedule getScheduleById(String id) {
091        return _scheduleHashTable.get(id);
092    }
093
094    /**
095     * Finds an existing schedule or creates a new schedule if needed requires
096     * schedule's name creates a unique id for this schedule
097     *
098     * @param name The string name of the schedule.
099     *
100     *
101     * @return new TrainSchedule or existing TrainSchedule
102     */
103    public TrainSchedule newSchedule(String name) {
104        TrainSchedule schedule = getScheduleByName(name);
105        if (schedule == null) {
106            _id++;
107            schedule = new TrainSchedule(Integer.toString(_id), name);
108            Integer oldSize = Integer.valueOf(_scheduleHashTable.size());
109            _scheduleHashTable.put(schedule.getId(), schedule);
110            setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize,
111                    Integer.valueOf(_scheduleHashTable.size()));
112        }
113        return schedule;
114    }
115
116    /**
117     * Remember a NamedBean Object created outside the manager.
118     *
119     * @param schedule The TrainSchedule to add.
120     */
121    public void register(TrainSchedule schedule) {
122        Integer oldSize = Integer.valueOf(_scheduleHashTable.size());
123        _scheduleHashTable.put(schedule.getId(), schedule);
124        // find last id created
125        int id = Integer.parseInt(schedule.getId());
126        if (id > _id) {
127            _id = id;
128        }
129        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(_scheduleHashTable.size()));
130    }
131
132    /**
133     * Forget a NamedBean Object created outside the manager.
134     *
135     * @param schedule The TrainSchedule to delete.
136     */
137    public void deregister(TrainSchedule schedule) {
138        if (schedule == null) {
139            return;
140        }
141        Integer oldSize = Integer.valueOf(_scheduleHashTable.size());
142        _scheduleHashTable.remove(schedule.getId());
143        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(_scheduleHashTable.size()));
144    }
145
146    /**
147     * Sort by train schedule name
148     *
149     * @return list of train schedules ordered by name
150     */
151    public List<TrainSchedule> getSchedulesByNameList() {
152        List<TrainSchedule> sortList = getList();
153        // now re-sort
154        List<TrainSchedule> out = new ArrayList<>();
155        for (int i = 0; i < sortList.size(); i++) {
156            for (int j = 0; j < out.size(); j++) {
157                if (sortList.get(i).getName().compareToIgnoreCase(out.get(j).getName()) < 0) {
158                    out.add(j, sortList.get(i));
159                    break;
160                }
161            }
162            if (!out.contains(sortList.get(i))) {
163                out.add(sortList.get(i));
164            }
165        }
166        return out;
167    }
168
169    /**
170     * Sort by train schedule id numbers
171     *
172     * @return list of train schedules ordered by id numbers
173     */
174    public List<TrainSchedule> getSchedulesByIdList() {
175        List<TrainSchedule> sortList = getList();
176        // now re-sort
177        List<TrainSchedule> out = new ArrayList<>();
178        for (int i = 0; i < sortList.size(); i++) {
179            for (int j = 0; j < out.size(); j++) {
180                try {
181                    if (Integer.parseInt(sortList.get(i).getId()) < Integer.parseInt(out.get(j).getId())) {
182                        out.add(j, sortList.get(i));
183                        break;
184                    }
185                } catch (NumberFormatException e) {
186                    log.debug("list id number isn't a number");
187                }
188            }
189            if (!out.contains(sortList.get(i))) {
190                out.add(sortList.get(i));
191            }
192        }
193        return out;
194    }
195
196    private List<TrainSchedule> getList() {
197        // no schedules? then load defaults
198        if (numEntries() == 0) {
199            createDefaultSchedules();
200        }
201        List<TrainSchedule> out = new ArrayList<>();
202        Enumeration<TrainSchedule> en = _scheduleHashTable.elements();
203        while (en.hasMoreElements()) {
204            out.add(en.nextElement());
205        }
206        return out;
207    }
208
209    /**
210     * Gets a JComboBox loaded with schedules starting with null.
211     *
212     * @return JComboBox with a list of schedules.
213     */
214    public JComboBox<TrainSchedule> getComboBox() {
215        JComboBox<TrainSchedule> box = new JComboBox<>();
216        updateComboBox(box);
217        return box;
218    }
219
220    /**
221     * Gets a JComboBox loaded with schedules starting with null.
222     *
223     * @return JComboBox with a list of schedules starting with null.
224     */
225    public JComboBox<TrainSchedule> getSelectComboBox() {
226        JComboBox<TrainSchedule> box = new JComboBox<>();
227        box.addItem(null);
228        for (TrainSchedule sch : getSchedulesByIdList()) {
229            box.addItem(sch);
230        }
231        return box;
232    }
233
234    /**
235     * Update a JComboBox with the latest schedules.
236     *
237     * @param box the JComboBox needing an update.
238     * @throws IllegalArgumentException if box is null
239     */
240    public void updateComboBox(JComboBox<TrainSchedule> box) {
241        if (box == null) {
242            throw new IllegalArgumentException("Attempt to update non-existant comboBox");
243        }
244        box.removeAllItems();
245        for (TrainSchedule sch : getSchedulesByNameList()) {
246            box.addItem(sch);
247        }
248    }
249
250    public void buildSwitchLists() {
251        TrainSwitchLists trainSwitchLists = new TrainSwitchLists();
252        TrainCsvSwitchLists trainCsvSwitchLists = new TrainCsvSwitchLists();
253        String locationName = ""; // only create switch lists once for locations with similar names
254        for (Location location : InstanceManager.getDefault(LocationManager.class).getLocationsByNameList()) {
255            if (location.isSwitchListEnabled() && !locationName.equals(location.getSplitName())) {
256                trainCsvSwitchLists.buildSwitchList(location);
257                trainSwitchLists.buildSwitchList(location);
258                locationName = location.getSplitName();
259                // print switch lists for locations that have changes
260                if (Setup.isSwitchListRealTime() && location.getStatus().equals(Location.UPDATED)) {
261                    trainSwitchLists.printSwitchList(location, InstanceManager.getDefault(TrainManager.class).isPrintPreviewEnabled());
262                }
263            }
264        }
265        // set trains switch lists printed
266        InstanceManager.getDefault(TrainManager.class).setTrainsSwitchListStatus(Train.PRINTED);
267    }
268
269    /**
270     * Create an XML element to represent this Entry. This member has to remain
271     * synchronized with the detailed DTD in operations-trains.dtd.
272     *
273     * @param root The common Element for operations-trains.dtd.
274     *
275     */
276    public void store(Element root) {
277        Element e = new Element(Xml.TRAIN_SCHEDULE_OPTIONS);
278        e.setAttribute(Xml.ACTIVE_ID, InstanceManager.getDefault(TrainScheduleManager.class).getTrainScheduleActiveId());
279        root.addContent(e);
280        Element values = new Element(Xml.SCHEDULES);
281        // add entries
282        List<TrainSchedule> schedules = getSchedulesByIdList();
283        for (TrainSchedule schedule : schedules) {
284            values.addContent(schedule.store());
285        }
286        root.addContent(values);
287    }
288
289    public void load(Element root) {
290        Element e = root.getChild(Xml.TRAIN_SCHEDULE_OPTIONS);
291        Attribute a;
292        if (e != null) {
293            if ((a = e.getAttribute(Xml.ACTIVE_ID)) != null) {
294                InstanceManager.getDefault(TrainScheduleManager.class).setTrainScheduleActiveId(a.getValue());
295            }
296        }
297
298        e = root.getChild(Xml.SCHEDULES);
299        if (e != null) {
300            List<Element> eSchedules = root.getChild(Xml.SCHEDULES).getChildren(Xml.SCHEDULE);
301            log.debug("TrainScheduleManager sees {} train schedules", eSchedules.size());
302            for (Element eSchedule : eSchedules) {
303                register(new TrainSchedule(eSchedule));
304            }
305        }
306    }
307
308    public void createDefaultSchedules() {
309        log.debug("creating default schedules");
310        newSchedule(Bundle.getMessage("Sunday"));
311        newSchedule(Bundle.getMessage("Monday"));
312        newSchedule(Bundle.getMessage("Tuesday"));
313        newSchedule(Bundle.getMessage("Wednesday"));
314        newSchedule(Bundle.getMessage("Thursday"));
315        newSchedule(Bundle.getMessage("Friday"));
316        newSchedule(Bundle.getMessage("Saturday"));
317    }
318
319    @Override
320    public void propertyChange(java.beans.PropertyChangeEvent e) {
321        log.debug("ScheduleManager sees property change: ({}) old: ({}) new ({})",
322                e.getPropertyName(), e.getOldValue(), e.getNewValue());
323    }
324
325    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
326        InstanceManager.getDefault(TrainManagerXml.class).setDirty(true);
327        firePropertyChange(p, old, n);
328    }
329
330    private final static Logger log = LoggerFactory.getLogger(TrainScheduleManager.class);
331
332    @Override
333    public void initialize() {
334        InstanceManager.getDefault(TrainManagerXml.class); // load trains
335    }
336
337}