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.OperationsPanel;
016import jmri.jmrit.operations.locations.Location;
017import jmri.jmrit.operations.locations.LocationManager;
018import jmri.jmrit.operations.setup.Setup;
019import jmri.jmrit.operations.trains.*;
020import jmri.jmrit.operations.trains.csv.TrainCsvSwitchLists;
021
022/**
023 * Manages train schedules. The default is the days of the week, but can be
024 * anything the user wants when defining when trains will run.
025 *
026 * @author Bob Jacobsen Copyright (C) 2003
027 * @author Daniel Boudreau Copyright (C) 2010
028 */
029public class TrainScheduleManager extends PropertyChangeSupport implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize, PropertyChangeListener {
030
031    public TrainScheduleManager() {
032    }
033
034    public static final String NONE = "";
035    private String _trainScheduleActiveId = NONE;
036    private int _id = 0;
037    
038    public static final String LISTLENGTH_CHANGED_PROPERTY = "trainScheduleListLength"; // NOI18N
039    public static final String SCHEDULE_ID_CHANGED_PROPERTY = "ActiveTrainScheduleId"; // NOI18N
040
041    public void dispose() {
042        _scheduleHashTable.clear();
043    }
044
045    // stores known TrainSchedule instances by id
046    protected Hashtable<String, TrainSchedule> _scheduleHashTable = new Hashtable<>();
047
048    /**
049     * @return Number of schedules
050     */
051    public int numEntries() {
052        return _scheduleHashTable.size();
053    }
054    
055    /**
056     * Sets the selected schedule id
057     *
058     * @param id Selected schedule id
059     */
060    public void setTrainScheduleActiveId(String id) {
061        String old = _trainScheduleActiveId;
062        _trainScheduleActiveId = id;
063        if (!old.equals(id)) {
064            setDirtyAndFirePropertyChange(SCHEDULE_ID_CHANGED_PROPERTY, old, id);
065        }
066    }
067
068    public String getTrainScheduleActiveId() {
069        return _trainScheduleActiveId;
070    }
071    
072    public TrainSchedule getActiveSchedule() {
073        return getScheduleById(getTrainScheduleActiveId());
074    }
075
076    /**
077     * @param name The schedule string name to search for.
078     * @return requested TrainSchedule object or null if none exists
079     */
080    public TrainSchedule getScheduleByName(String name) {
081        TrainSchedule s;
082        Enumeration<TrainSchedule> en = _scheduleHashTable.elements();
083        while (en.hasMoreElements()) {
084            s = en.nextElement();
085            if (s.getName().equals(name)) {
086                return s;
087            }
088        }
089        return null;
090    }
091
092    public TrainSchedule getScheduleById(String id) {
093        return _scheduleHashTable.get(id);
094    }
095
096    /**
097     * Finds an existing schedule or creates a new schedule if needed requires
098     * schedule's name creates a unique id for this schedule
099     *
100     * @param name The string name of the schedule.
101     *
102     *
103     * @return new TrainSchedule or existing TrainSchedule
104     */
105    public TrainSchedule newSchedule(String name) {
106        TrainSchedule schedule = getScheduleByName(name);
107        if (schedule == null) {
108            _id++;
109            schedule = new TrainSchedule(Integer.toString(_id), name);
110            int oldSize = _scheduleHashTable.size();
111            _scheduleHashTable.put(schedule.getId(), schedule);
112            setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _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        int oldSize = _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, _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        int oldSize = _scheduleHashTable.size();
143        _scheduleHashTable.remove(schedule.getId());
144        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _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        OperationsPanel.padComboBox(box);
219        return box;
220    }
221
222    /**
223     * Gets a JComboBox loaded with schedules starting with null.
224     *
225     * @return JComboBox with a list of schedules starting with null.
226     */
227    public JComboBox<TrainSchedule> getSelectComboBox() {
228        JComboBox<TrainSchedule> box = new JComboBox<>();
229        box.addItem(null);
230        for (TrainSchedule sch : getSchedulesByIdList()) {
231            box.addItem(sch);
232        }
233        OperationsPanel.padComboBox(box);
234        return box;
235    }
236
237    /**
238     * Update a JComboBox with the latest schedules.
239     *
240     * @param box the JComboBox needing an update.
241     * @throws IllegalArgumentException if box is null
242     */
243    public void updateComboBox(JComboBox<TrainSchedule> box) {
244        if (box == null) {
245            throw new IllegalArgumentException("Attempt to update non-existant comboBox");
246        }
247        box.removeAllItems();
248        for (TrainSchedule sch : getSchedulesByNameList()) {
249            box.addItem(sch);
250        }
251    }
252
253    public void buildSwitchLists() {
254        TrainSwitchLists trainSwitchLists = new TrainSwitchLists();
255        TrainCsvSwitchLists trainCsvSwitchLists = new TrainCsvSwitchLists();
256        String locationName = ""; // only create switch lists once for locations with similar names
257        for (Location location : InstanceManager.getDefault(LocationManager.class).getLocationsByNameList()) {
258            if (location.isSwitchListEnabled() && !locationName.equals(location.getSplitName())) {
259                trainCsvSwitchLists.buildSwitchList(location);
260                trainSwitchLists.buildSwitchList(location);
261                locationName = location.getSplitName();
262                // print switch lists for locations that have changes
263                if (Setup.isSwitchListRealTime() && location.getStatus().equals(Location.UPDATED)) {
264                    trainSwitchLists.printSwitchList(location, InstanceManager.getDefault(TrainManager.class).isPrintPreviewEnabled());
265                }
266            }
267        }
268        // set trains switch lists printed
269        InstanceManager.getDefault(TrainManager.class).setTrainsSwitchListStatus(Train.PRINTED);
270    }
271
272    /**
273     * Create an XML element to represent this Entry. This member has to remain
274     * synchronized with the detailed DTD in operations-trains.dtd.
275     *
276     * @param root The common Element for operations-trains.dtd.
277     *
278     */
279    public void store(Element root) {
280        Element e = new Element(Xml.TRAIN_SCHEDULE_OPTIONS);
281        e.setAttribute(Xml.ACTIVE_ID, InstanceManager.getDefault(TrainScheduleManager.class).getTrainScheduleActiveId());
282        root.addContent(e);
283        Element values = new Element(Xml.SCHEDULES);
284        // add entries
285        List<TrainSchedule> schedules = getSchedulesByIdList();
286        for (TrainSchedule schedule : schedules) {
287            values.addContent(schedule.store());
288        }
289        root.addContent(values);
290    }
291
292    public void load(Element root) {
293        Element e = root.getChild(Xml.TRAIN_SCHEDULE_OPTIONS);
294        Attribute a;
295        if (e != null) {
296            if ((a = e.getAttribute(Xml.ACTIVE_ID)) != null) {
297                setTrainScheduleActiveId(a.getValue());
298            }
299        }
300
301        e = root.getChild(Xml.SCHEDULES);
302        if (e != null) {
303            List<Element> eSchedules = root.getChild(Xml.SCHEDULES).getChildren(Xml.SCHEDULE);
304            log.debug("TrainScheduleManager sees {} train schedules", eSchedules.size());
305            for (Element eSchedule : eSchedules) {
306                register(new TrainSchedule(eSchedule));
307            }
308        }
309    }
310
311    public void createDefaultSchedules() {
312        log.debug("creating default schedules");
313        for (String s : getDaysOfWeek()) {
314            newSchedule(s);
315        }
316    }
317    
318    public String[] getDaysOfWeek() {
319        String[] s = {Bundle.getMessage("Sunday"), Bundle.getMessage("Monday"), Bundle.getMessage("Tuesday"),
320                Bundle.getMessage("Wednesday"), Bundle.getMessage("Thursday"), Bundle.getMessage("Friday"),
321                Bundle.getMessage("Saturday")};
322        return s;
323    }
324
325    @Override
326    public void propertyChange(java.beans.PropertyChangeEvent e) {
327        log.debug("ScheduleManager sees property change: ({}) old: ({}) new ({})",
328                e.getPropertyName(), e.getOldValue(), e.getNewValue());
329    }
330
331    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
332        InstanceManager.getDefault(TrainManagerXml.class).setDirty(true);
333        firePropertyChange(p, old, n);
334    }
335
336    private static final Logger log = LoggerFactory.getLogger(TrainScheduleManager.class);
337
338    @Override
339    public void initialize() {
340        InstanceManager.getDefault(TrainManagerXml.class); // load trains
341    }
342
343}