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