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