001package jmri.jmrit.operations.trains.manualtrainbuilder;
002
003import java.beans.PropertyChangeListener;
004import java.util.*;
005
006import org.jdom2.Element;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010import jmri.*;
011import jmri.beans.PropertyChangeSupport;
012import jmri.jmrit.operations.locations.LocationManagerXml;
013import jmri.jmrit.operations.locations.Track;
014import jmri.jmrit.operations.rollingstock.cars.CarRoads;
015import jmri.jmrit.operations.rollingstock.cars.CarTypes;
016import jmri.jmrit.operations.setup.Control;
017
018/**
019 * Manages train manual builds
020 *
021 * @author Daniel Boudreau Copyright (C) 2026
022 */
023public class TrainManualBuildManager extends PropertyChangeSupport implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize, PropertyChangeListener {
024
025    public static final String LISTLENGTH_CHANGED_PROPERTY = "manualBuildListLength"; // NOI18N
026
027    public TrainManualBuildManager() {
028    }
029
030    private int _id = 0;
031
032    public void dispose() {
033        _manualBuildHashTable.clear();
034    }
035
036    // stores known ManualBuild instances by id
037    protected Hashtable<String, TrainManualBuild> _manualBuildHashTable = new Hashtable<String, TrainManualBuild>();
038
039    /**
040     * @return Number of manual builds
041     */
042    public int numEntries() {
043        return _manualBuildHashTable.size();
044    }
045
046    /**
047     * @param trainId The train id for the manual build
048     * @return requested ManualBuild object or null if none exists
049     */
050    public TrainManualBuild getManualBuildByTrainId(String trainId) {
051        TrainManualBuild mb;
052        Enumeration<TrainManualBuild> en = _manualBuildHashTable.elements();
053        while (en.hasMoreElements()) {
054            mb = en.nextElement();
055            if (mb.getTrainId().equals(trainId)) {
056                return mb;
057            }
058        }
059        return null;
060    }
061
062    public TrainManualBuild getManualBuildById(String id) {
063        return _manualBuildHashTable.get(id);
064    }
065
066    /**
067     * Finds an existing manual build or creates a new manual build if needed.
068     *
069     * @param trainId The train id for this manual build
070     *
071     *
072     * @return new manual build or existing manual build
073     */
074    public TrainManualBuild newManualBuild(String trainId) {
075        TrainManualBuild mb = getManualBuildByTrainId(trainId);
076        if (mb == null && !trainId.isBlank()) {
077            _id++;
078            mb = new TrainManualBuild(Integer.toString(_id), trainId);
079            int oldSize = _manualBuildHashTable.size();
080            _manualBuildHashTable.put(mb.getId(), mb);
081            setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _manualBuildHashTable.size());
082        }
083        return mb;
084    }
085
086    /**
087     * Remember a NamedBean Object created outside the manager.
088     *
089     * @param manualBuild The ManualBuild to add.
090     */
091    public void register(TrainManualBuild manualBuild) {
092        int oldSize = _manualBuildHashTable.size();
093        _manualBuildHashTable.put(manualBuild.getId(), manualBuild);
094        // find last id created
095        int id = Integer.parseInt(manualBuild.getId());
096        if (id > _id) {
097            _id = id;
098        }
099        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _manualBuildHashTable.size());
100    }
101
102    /**
103     * Forget a NamedBean Object created outside the manager.
104     *
105     * @param manualBuild The ManualBuild to delete.
106     */
107    public void deregister(TrainManualBuild manualBuild) {
108        if (manualBuild == null) {
109            return;
110        }
111        manualBuild.dispose();
112        int oldSize = _manualBuildHashTable.size();
113        _manualBuildHashTable.remove(manualBuild.getId());
114        setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _manualBuildHashTable.size());
115    }
116
117    /**
118     * Sort by manual build train names
119     *
120     * @return list of manual builds ordered by train names
121     */
122    public List<TrainManualBuild> getManualBuildsByTrainNameList() {
123        List<TrainManualBuild> sortList = getList();
124        // now re-sort
125        List<TrainManualBuild> out = new ArrayList<TrainManualBuild>();
126        for (TrainManualBuild mb : sortList) {
127            for (int j = 0; j < out.size(); j++) {
128                if (mb.getTrainName().compareToIgnoreCase(out.get(j).getTrainName()) < 0) {
129                    out.add(j, mb);
130                    break;
131                }
132            }
133            if (!out.contains(mb)) {
134                out.add(mb);
135            }
136        }
137        return out;
138
139    }
140
141    /**
142     * Sort by manual build id number
143     *
144     * @return list of manual builds ordered by id number
145     */
146    public List<TrainManualBuild> getManualBuildsByIdList() {
147        List<TrainManualBuild> sortList = getList();
148        // now re-sort
149        List<TrainManualBuild> out = new ArrayList<TrainManualBuild>();
150        for (TrainManualBuild mb : sortList) {
151            for (int j = 0; j < out.size(); j++) {
152                try {
153                    if (Integer.parseInt(mb.getId()) < Integer.parseInt(out.get(j).getId())) {
154                        out.add(j, mb);
155                        break;
156                    }
157                } catch (NumberFormatException e) {
158                    log.debug("list id number isn't a number");
159                }
160            }
161            if (!out.contains(mb)) {
162                out.add(mb);
163            }
164        }
165        return out;
166    }
167
168    private List<TrainManualBuild> getList() {
169        List<TrainManualBuild> out = new ArrayList<TrainManualBuild>();
170        Enumeration<TrainManualBuild> en = _manualBuildHashTable.elements();
171        while (en.hasMoreElements()) {
172            out.add(en.nextElement());
173        }
174        return out;
175    }
176
177    public TrainManualBuild copyManualBuild(TrainManualBuild manualBuild, String newManualBuildName) {
178        TrainManualBuild newManualBuild = newManualBuild(newManualBuildName);
179        for (TrainManualBuildItem mbi : manualBuild.getItemsBySequenceList()) {
180            TrainManualBuildItem newMbi = newManualBuild.addItem();
181            newMbi.copyManualBuildItem(mbi);
182        }
183        return newManualBuild;
184    }
185
186 
187    /**
188     * Replaces car type in all manual builds.
189     *
190     * @param oldType car type to be replaced.
191     * @param newType replacement car type.
192     */
193    public void replaceType(String oldType, String newType) {
194        for (TrainManualBuild sch : getList()) {
195            for (TrainManualBuildItem si : sch.getItemsBySequenceList()) {
196                if (si.getTypeName().equals(oldType)) {
197                    si.setTypeName(newType);
198                }
199            }
200        }
201    }
202
203    /**
204     * Replaces car roads in all manual builds.
205     *
206     * @param oldRoad car road to be replaced.
207     * @param newRoad replacement car road.
208     */
209    public void replaceRoad(String oldRoad, String newRoad) {
210        if (newRoad == null) {
211            return;
212        }
213        for (TrainManualBuild mb : getList()) {
214            for (TrainManualBuildItem mbi : mb.getItemsBySequenceList()) {
215                if (mbi.getRoadName().equals(oldRoad)) {
216                    mbi.setRoadName(newRoad);
217                }
218            }
219        }
220    }
221
222    /**
223     * Replaces car loads in all manual builds with specific car type.
224     *
225     * @param type    car type.
226     * @param oldLoad car load to be replaced.
227     * @param newLoad replacement car load.
228     */
229    public void replaceLoad(String type, String oldLoad, String newLoad) {
230        for (TrainManualBuild mb : getList()) {
231            for (TrainManualBuildItem mbi : mb.getItemsBySequenceList()) {
232                if (mbi.getTypeName().equals(type) && mbi.getLoadName().equals(oldLoad)) {
233                    if (newLoad != null) {
234                        mbi.setLoadName(newLoad);
235                    } else {
236                        mbi.setLoadName(TrainManualBuildItem.NONE);
237                    }
238                }
239            }
240        }
241    }
242
243    public void replaceTrack(Track oldTrack, Track newTrack) {
244        for (TrainManualBuild mb : getList()) {
245            for (TrainManualBuildItem mbi : mb.getItemsBySequenceList()) {
246                if (mbi.getDestinationTrack() == oldTrack) {
247                    mbi.setDestination(newTrack.getLocation());
248                    mbi.setDestinationTrack(newTrack);
249                }
250            }
251        }
252    }
253
254    public void load(Element root) {
255        if (root.getChild(Xml.MANUAL_BUILDS) != null) {
256            List<Element> eManualBuilds = root.getChild(Xml.MANUAL_BUILDS).getChildren(Xml.MANUAL_BUILD);
257            log.debug("readFile sees {} manual builds", eManualBuilds.size());
258            for (Element eManualBuild : eManualBuilds) {
259                register(new TrainManualBuild(eManualBuild));
260            }
261        }
262    }
263
264    public void store(Element root) {
265        Element values;
266        root.addContent(values = new Element(Xml.MANUAL_BUILDS));
267        // add entries
268        for (TrainManualBuild manualBuild : getManualBuildsByIdList()) {
269            values.addContent(manualBuild.store());
270        }
271    }
272
273    /**
274     * Check for car type and road name changes.
275     */
276    @Override
277    public void propertyChange(java.beans.PropertyChangeEvent e) {
278        if (Control.SHOW_PROPERTY) {
279            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e
280                    .getNewValue());
281        }
282        if (e.getPropertyName().equals(CarTypes.CARTYPES_NAME_CHANGED_PROPERTY)) {
283            replaceType((String) e.getOldValue(), (String) e.getNewValue());
284        }
285        if (e.getPropertyName().equals(CarRoads.CARROADS_NAME_CHANGED_PROPERTY)) {
286            replaceRoad((String) e.getOldValue(), (String) e.getNewValue());
287        }
288    }
289
290    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
291        // set dirty
292        InstanceManager.getDefault(LocationManagerXml.class).setDirty(true);
293        firePropertyChange(p, old, n);
294    }
295
296    private static final Logger log = LoggerFactory.getLogger(TrainManualBuildManager.class);
297
298    @Override
299    public void initialize() {
300        InstanceManager.getDefault(CarTypes.class).addPropertyChangeListener(this);
301        InstanceManager.getDefault(CarRoads.class).addPropertyChangeListener(this);
302    }
303
304}