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     * @return new route or existing route
068     */
069    public Route newRoute(String name) {
070        Route route = getRouteByName(name);
071        if (route == null) {
072            _id++;
073            route = new Route(Integer.toString(_id), name);
074            int oldSize = _routeHashTable.size();
075            _routeHashTable.put(route.getId(), route);
076            setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _routeHashTable.size());
077        }
078        return route;
079    }
080
081    /**
082     * Remember a NamedBean Object created outside the manager.
083     *
084     * @param route The Route to add.
085     */
086    public void register(Route route) {
087        int oldSize = _routeHashTable.size();
088        _routeHashTable.put(route.getId(), route);
089        // find last id created
090        int id = Integer.parseInt(route.getId());
091        if (id > _id) {
092            _id = id;
093        }
094        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _routeHashTable.size());
095        // listen for name and state changes to forward
096    }
097
098    /**
099     * Forget a NamedBean Object created outside the manager.
100     *
101     * @param route The Route to delete.
102     */
103    public void deregister(Route route) {
104        if (route == null) {
105            return;
106        }
107        route.dispose();
108        int oldSize = _routeHashTable.size();
109        _routeHashTable.remove(route.getId());
110        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _routeHashTable.size());
111    }
112
113    /**
114     * Sort by route name
115     *
116     * @return list of routes ordered by name
117     */
118    public List<Route> getRoutesByNameList() {
119        List<Route> sortList = getList();
120        // now re-sort
121        List<Route> out = new ArrayList<>();
122        for (Route route : sortList) {
123            for (int j = 0; j < out.size(); j++) {
124                if (route.getName().compareToIgnoreCase(out.get(j).getName()) < 0) {
125                    out.add(j, route);
126                    break;
127                }
128            }
129            if (!out.contains(route)) {
130                out.add(route);
131            }
132        }
133        return out;
134
135    }
136
137    /**
138     * Sort by route number, number can alpha numeric
139     *
140     * @return list of routes ordered by id numbers
141     */
142    public List<Route> getRoutesByIdList() {
143        List<Route> sortList = getList();
144        // now re-sort
145        List<Route> out = new ArrayList<>();
146        for (Route route : sortList) {
147            for (int j = 0; j < out.size(); j++) {
148                try {
149                    if (Integer.parseInt(route.getId()) < Integer.parseInt(out.get(j).getId())) {
150                        out.add(j, route);
151                        break;
152                    }
153                } catch (NumberFormatException e) {
154                    log.error("list id number isn't a number");
155                }
156            }
157            if (!out.contains(route)) {
158                out.add(route);
159            }
160        }
161        return out;
162    }
163
164    private List<Route> getList() {
165        List<Route> out = new ArrayList<>();
166        Enumeration<Route> en = _routeHashTable.elements();
167        while (en.hasMoreElements()) {
168            out.add(en.nextElement());
169        }
170        return out;
171    }
172
173    public RouteLocation getRouteLocationById(String id) {
174        for (Route route : getList()) {
175            for (RouteLocation rl : route.getLocationsBySequenceList()) {
176                if (rl.getId().equals(id)) {
177                    return rl;
178                }
179            }
180        }
181        return null; // not found
182    }
183
184    /**
185     * Used to determine if a location is part of any route.
186     * 
187     * @param loc The location being checked.
188     * @return null if location isn't used, otherwise a route using the
189     *         location.
190     */
191    public Route isLocationInUse(Location loc) {
192        for (Route route : getList()) {
193            RouteLocation rl = route.getLastLocationByName(loc.getName());
194            if (rl != null) {
195                return route;
196            }
197        }
198        return null;
199    }
200
201    public JComboBox<Route> getComboBox() {
202        JComboBox<Route> box = new JComboBox<>();
203        box.addItem(null);
204        List<Route> routes = getRoutesByNameList();
205        for (Route route : routes) {
206            box.addItem(route);
207        }
208        OperationsPanel.padComboBox(box, Control.max_len_string_route_name);
209        return box;
210    }
211
212    public void updateComboBox(JComboBox<Route> box) {
213        box.removeAllItems();
214        box.addItem(null);
215        List<Route> routes = getRoutesByNameList();
216        for (Route route : routes) {
217            box.addItem(route);
218        }
219    }
220
221    /**
222     * Copy route, returns a new route named routeName. If invert is true the
223     * reverse of the route is returned.
224     *
225     * @param route     The route to be copied
226     * @param routeName The name of the new route
227     * @param invert    If true, return the inversion of route
228     * @return A copy of the route
229     */
230    public Route copyRoute(Route route, String routeName, boolean invert) {
231        Route newRoute = newRoute(routeName);
232        List<RouteLocation> routeList = route.getLocationsBySequenceList();
233        if (!invert) {
234            for (RouteLocation rl : routeList) {
235                copyRouteLocation(newRoute, rl, null, invert);
236            }
237            // invert route order
238        } else {
239            for (int i = routeList.size() - 1; i >= 0; i--) {
240                int y = i - 1;
241                if (y < 0) {
242                    y = 0;
243                }
244                copyRouteLocation(newRoute, routeList.get(i), routeList.get(y), invert);
245            }
246        }
247        newRoute.setComment(route.getComment());
248        return newRoute;
249    }
250
251    private void copyRouteLocation(Route newRoute, RouteLocation rl, RouteLocation rlNext, boolean invert) {
252        Location loc = InstanceManager.getDefault(LocationManager.class).getLocationByName(rl.getName());
253        RouteLocation rlNew = newRoute.addLocation(loc);
254        // now copy the route location objects we want
255        rlNew.setLocalMovesAllowed(rl.isLocalMovesAllowed());
256        rlNew.setMaxCarMoves(rl.getMaxCarMoves());
257        rlNew.setRandomControl(rl.getRandomControl());
258        rlNew.setWait(rl.getWait());
259        rlNew.setDepartureTimeHourMinutes(rl.getDepartureTimeHourMinutes());
260        rlNew.setDepartureTimeDay(rl.getDepartureTimeDay());
261        rlNew.setComment(rl.getComment());
262        rlNew.setCommentColor(rl.getCommentColor());
263        if (!invert) {
264            rlNew.setDropAllowed(rl.isDropAllowed());
265            rlNew.setPickUpAllowed(rl.isPickUpAllowed());
266            rlNew.setGrade(rl.getGrade());
267            rlNew.setTrainDirection(rl.getTrainDirection());
268            rlNew.setMaxTrainLength(rl.getMaxTrainLength());
269        } else {
270            // flip set outs and pick ups
271            rlNew.setDropAllowed(rl.isPickUpAllowed());
272            rlNew.setPickUpAllowed(rl.isDropAllowed());
273            // invert train directions
274            int oldDirection = rl.getTrainDirection();
275            if (oldDirection == RouteLocation.NORTH) {
276                rlNew.setTrainDirection(RouteLocation.SOUTH);
277            } else if (oldDirection == RouteLocation.SOUTH) {
278                rlNew.setTrainDirection(RouteLocation.NORTH);
279            } else if (oldDirection == RouteLocation.EAST) {
280                rlNew.setTrainDirection(RouteLocation.WEST);
281            } else if (oldDirection == RouteLocation.WEST) {
282                rlNew.setTrainDirection(RouteLocation.EAST);
283            }
284            // get the max length between location
285            if (rlNext == null) {
286                log.error("Can not copy route, rlNext is null!");
287                return;
288            }
289            rlNew.setMaxTrainLength(rlNext.getMaxTrainLength());
290        }
291        rlNew.setTrainIconX(rl.getTrainIconX());
292        rlNew.setTrainIconY(rl.getTrainIconY());
293    }
294
295    /**
296     * @return Number of routes
297     */
298    public int numEntries() {
299        return _routeHashTable.size();
300    }
301
302    public void load(Element root) {
303        // decode type, invoke proper processing routine if a decoder file
304        if (root.getChild(Xml.ROUTES) != null) {
305            List<Element> eRoutes = root.getChild(Xml.ROUTES).getChildren(Xml.ROUTE);
306            log.debug("readFile sees {} routes", eRoutes.size());
307            for (Element eRoute : eRoutes) {
308                register(new Route(eRoute));
309            }
310        }
311    }
312
313    public void store(Element root) {
314        Element values = new Element(Xml.ROUTES);
315        root.addContent(values);
316        for (Route route : getRoutesByIdList()) {
317            values.addContent(route.store());
318        }
319    }
320
321    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
322        InstanceManager.getDefault(RouteManagerXml.class).setDirty(true);
323        firePropertyChange(p, old, n);
324    }
325
326    private static final Logger log = LoggerFactory.getLogger(RouteManager.class);
327
328    @Override
329    public void initialize() {
330        InstanceManager.getDefault(OperationsSetupXml.class); // load setup
331        InstanceManager.getDefault(RouteManagerXml.class); // load routes
332    }
333
334}