001package jmri.jmrit.operations.routes;
002
003import java.util.ArrayList;
004import java.util.Enumeration;
005import java.util.Hashtable;
006import java.util.List;
007
008import javax.swing.JComboBox;
009
010import org.jdom2.Element;
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013
014import jmri.InstanceManager;
015import jmri.InstanceManagerAutoDefault;
016import jmri.InstanceManagerAutoInitialize;
017import jmri.beans.PropertyChangeSupport;
018import jmri.jmrit.operations.locations.Location;
019import jmri.jmrit.operations.locations.LocationManager;
020import jmri.jmrit.operations.setup.OperationsSetupXml;
021
022/**
023 * Manages the routes
024 *
025 * @author Bob Jacobsen Copyright (C) 2003
026 * @author Daniel Boudreau Copyright (C) 2008, 2009, 2010
027 */
028public class RouteManager extends PropertyChangeSupport implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize {
029
030    public static final String LISTLENGTH_CHANGED_PROPERTY = "routesListLengthChanged"; // NOI18N
031
032    public RouteManager() {
033    }
034
035    private int _id = 0;
036
037    public void dispose() {
038        _routeHashTable.clear();
039        _id = 0;
040    }
041
042    // stores known Route instances by id
043    protected Hashtable<String, Route> _routeHashTable = new Hashtable<>();
044
045    /**
046     * @param name The string name of the Route.
047     * @return requested Route object or null if none exists
048     */
049    public Route getRouteByName(String name) {
050        Route l;
051        Enumeration<Route> en = _routeHashTable.elements();
052        while (en.hasMoreElements()) {
053            l = en.nextElement();
054            if (l.getName().equals(name)) {
055                return l;
056            }
057        }
058        return null;
059    }
060
061    public Route getRouteById(String id) {
062        return _routeHashTable.get(id);
063    }
064
065    /**
066     * Finds an existing route or creates a new route if needed requires route's
067     * name creates a unique id for this route
068     *
069     * @param name The string name of the new Route.
070     *
071     *
072     * @return new route or existing route
073     */
074    public Route newRoute(String name) {
075        Route route = getRouteByName(name);
076        if (route == null) {
077            _id++;
078            route = new Route(Integer.toString(_id), name);
079            Integer oldSize = Integer.valueOf(_routeHashTable.size());
080            _routeHashTable.put(route.getId(), route);
081            setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize,
082                    Integer.valueOf(_routeHashTable.size()));
083        }
084        return route;
085    }
086
087    /**
088     * Remember a NamedBean Object created outside the manager.
089     *
090     * @param route The Route to add.
091     */
092    public void register(Route route) {
093        Integer oldSize = Integer.valueOf(_routeHashTable.size());
094        _routeHashTable.put(route.getId(), route);
095        // find last id created
096        int id = Integer.parseInt(route.getId());
097        if (id > _id) {
098            _id = id;
099        }
100        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(_routeHashTable.size()));
101        // listen for name and state changes to forward
102    }
103
104    /**
105     * Forget a NamedBean Object created outside the manager.
106     *
107     * @param route The Route to delete.
108     */
109    public void deregister(Route route) {
110        if (route == null) {
111            return;
112        }
113        route.dispose();
114        Integer oldSize = Integer.valueOf(_routeHashTable.size());
115        _routeHashTable.remove(route.getId());
116        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, Integer.valueOf(_routeHashTable.size()));
117    }
118
119    /**
120     * Sort by route name
121     *
122     * @return list of routes ordered by name
123     */
124    public List<Route> getRoutesByNameList() {
125        List<Route> sortList = getList();
126        // now re-sort
127        List<Route> out = new ArrayList<>();
128        for (Route route : sortList) {
129            for (int j = 0; j < out.size(); j++) {
130                if (route.getName().compareToIgnoreCase(out.get(j).getName()) < 0) {
131                    out.add(j, route);
132                    break;
133                }
134            }
135            if (!out.contains(route)) {
136                out.add(route);
137            }
138        }
139        return out;
140
141    }
142
143    /**
144     * Sort by route number, number can alpha numeric
145     *
146     * @return list of routes ordered by id numbers
147     */
148    public List<Route> getRoutesByIdList() {
149        List<Route> sortList = getList();
150        // now re-sort
151        List<Route> out = new ArrayList<>();
152        for (Route route : sortList) {
153            for (int j = 0; j < out.size(); j++) {
154                try {
155                    if (Integer.parseInt(route.getId()) < Integer.parseInt(out.get(j).getId())) {
156                        out.add(j, route);
157                        break;
158                    }
159                } catch (NumberFormatException e) {
160                    log.error("list id number isn't a number");
161                }
162            }
163            if (!out.contains(route)) {
164                out.add(route);
165            }
166        }
167        return out;
168    }
169
170    private List<Route> getList() {
171        List<Route> out = new ArrayList<>();
172        Enumeration<Route> en = _routeHashTable.elements();
173        while (en.hasMoreElements()) {
174            out.add(en.nextElement());
175        }
176        return out;
177    }
178
179    public JComboBox<Route> getComboBox() {
180        JComboBox<Route> box = new JComboBox<>();
181        box.addItem(null);
182        List<Route> routes = getRoutesByNameList();
183        for (Route route : routes) {
184            box.addItem(route);
185        }
186        return box;
187    }
188
189    public void updateComboBox(JComboBox<Route> box) {
190        box.removeAllItems();
191        box.addItem(null);
192        List<Route> routes = getRoutesByNameList();
193        for (Route route : routes) {
194            box.addItem(route);
195        }
196    }
197
198    /**
199     * Copy route, returns a new route named routeName. If invert is true the
200     * reverse of the route is returned.
201     *
202     * @param route     The route to be copied
203     * @param routeName The name of the new route
204     * @param invert    If true, return the inversion of route
205     * @return A copy of the route
206     */
207    public Route copyRoute(Route route, String routeName, boolean invert) {
208        Route newRoute = newRoute(routeName);
209        List<RouteLocation> routeList = route.getLocationsBySequenceList();
210        if (!invert) {
211            for (RouteLocation rl : routeList) {
212                copyRouteLocation(newRoute, rl, null, invert);
213            }
214            // invert route order
215        } else {
216            for (int i = routeList.size() - 1; i >= 0; i--) {
217                int y = i - 1;
218                if (y < 0) {
219                    y = 0;
220                }
221                copyRouteLocation(newRoute, routeList.get(i), routeList.get(y), invert);
222            }
223        }
224        newRoute.setComment(route.getComment());
225        return newRoute;
226    }
227
228    private void copyRouteLocation(Route newRoute, RouteLocation rl, RouteLocation rlNext, boolean invert) {
229        Location loc = InstanceManager.getDefault(LocationManager.class).getLocationByName(rl.getName());
230        RouteLocation rlNew = newRoute.addLocation(loc);
231        // now copy the route location objects we want
232        rlNew.setMaxCarMoves(rl.getMaxCarMoves());
233        rlNew.setRandomControl(rl.getRandomControl());
234        rlNew.setWait(rl.getWait());
235        rlNew.setDepartureTime(rl.getDepartureTime());
236        rlNew.setComment(rl.getComment());
237        rlNew.setCommentColor(rl.getCommentColor());
238        if (!invert) {
239            rlNew.setDropAllowed(rl.isDropAllowed());
240            rlNew.setPickUpAllowed(rl.isPickUpAllowed());
241            rlNew.setGrade(rl.getGrade());
242            rlNew.setTrainDirection(rl.getTrainDirection());
243            rlNew.setMaxTrainLength(rl.getMaxTrainLength());
244        } else {
245            // flip set outs and pick ups
246            rlNew.setDropAllowed(rl.isPickUpAllowed());
247            rlNew.setPickUpAllowed(rl.isDropAllowed());
248            // invert train directions
249            int oldDirection = rl.getTrainDirection();
250            if (oldDirection == RouteLocation.NORTH) {
251                rlNew.setTrainDirection(RouteLocation.SOUTH);
252            } else if (oldDirection == RouteLocation.SOUTH) {
253                rlNew.setTrainDirection(RouteLocation.NORTH);
254            } else if (oldDirection == RouteLocation.EAST) {
255                rlNew.setTrainDirection(RouteLocation.WEST);
256            } else if (oldDirection == RouteLocation.WEST) {
257                rlNew.setTrainDirection(RouteLocation.EAST);
258            }
259            // get the max length between location
260            if (rlNext == null) {
261                log.error("Can not copy route, rlNext is null!");
262                return;
263            }
264            rlNew.setMaxTrainLength(rlNext.getMaxTrainLength());
265        }
266        rlNew.setTrainIconX(rl.getTrainIconX());
267        rlNew.setTrainIconY(rl.getTrainIconY());
268    }
269
270    /**
271     * @return Number of routes
272     */
273    public int numEntries() {
274        return _routeHashTable.size();
275    }
276
277    public void load(Element root) {
278        // decode type, invoke proper processing routine if a decoder file
279        if (root.getChild(Xml.ROUTES) != null) {
280            List<Element> eRoutes = root.getChild(Xml.ROUTES).getChildren(Xml.ROUTE);
281            log.debug("readFile sees {} routes", eRoutes.size());
282            for (Element eRoute : eRoutes) {
283                register(new Route(eRoute));
284            }
285        }
286    }
287
288    public void store(Element root) {
289        Element values = new Element(Xml.ROUTES);
290        root.addContent(values);
291        for (Route route : getRoutesByIdList()) {
292            values.addContent(route.store());
293        }
294    }
295
296    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
297        InstanceManager.getDefault(RouteManagerXml.class).setDirty(true);
298        firePropertyChange(p, old, n);
299    }
300
301    private final static Logger log = LoggerFactory.getLogger(RouteManager.class);
302
303    @Override
304    public void initialize() {
305        InstanceManager.getDefault(OperationsSetupXml.class); // load setup
306        InstanceManager.getDefault(RouteManagerXml.class); // load routes
307    }
308
309}