001package jmri.jmrit.operations.automation;
002
003import java.util.ArrayList;
004import java.util.List;
005
006import javax.swing.JComboBox;
007
008import org.jdom2.Element;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012import jmri.InstanceManager;
013import jmri.beans.PropertyChangeSupport;
014import jmri.jmrit.operations.automation.actions.*;
015import jmri.jmrit.operations.routes.RouteLocation;
016import jmri.jmrit.operations.setup.Control;
017import jmri.jmrit.operations.trains.Train;
018import jmri.jmrit.operations.trains.TrainManager;
019import jmri.jmrit.operations.trains.TrainManagerXml;
020import jmri.jmrit.operations.trains.schedules.TrainSchedule;
021import jmri.jmrit.operations.trains.schedules.TrainScheduleManager;
022
023/**
024 * Represents one automation item of a automation
025 *
026 * @author Daniel Boudreau Copyright (C) 2016
027 */
028public class AutomationItem extends PropertyChangeSupport implements java.beans.PropertyChangeListener {
029
030    public static final String NONE = ""; // NOI18N
031
032    protected String _id = NONE;
033    protected int _sequenceId = 0; // used to determine order in automation
034    
035    protected boolean _actionRunning = false; // when true action is running, for example waiting for a train
036    protected boolean _actionSuccessful = false;
037    protected boolean _actionRan = false;
038    protected boolean _haltFail = true;
039    
040    protected Action _action = null;
041    protected String _message = NONE;
042    protected String _messageFail = NONE;
043    
044    // the following are associated with actions
045    protected Train _train = null;
046    protected RouteLocation _routeLocation = null;
047    protected String _automationIdToRun = NONE;
048    protected String _gotoAutomationItemId = NONE; // the goto automationItem
049    protected boolean _gotoAutomationBranched = false;
050    protected String _trainScheduleId = NONE;
051
052    public static final String DISPOSE = "automationItemDispose"; // NOI18N
053
054    public AutomationItem(String id) {
055        log.debug("New automation item id: {}", id);
056        _id = id;
057        setAction(new NoAction()); // the default
058    }
059
060    public String getId() {
061        return _id;
062    }
063
064    @Override
065    public String toString() {
066        return getId(); // for property changes
067    }
068
069    public int getSequenceId() {
070        return _sequenceId;
071    }
072
073    public void setSequenceId(int sequence) {
074        // property change not needed
075        _sequenceId = sequence;
076    }
077
078    public void setAction(Action action) {
079        Action old = _action;
080        _action = action;
081        if (old != null) {
082            old.cancelAction();
083        }
084        if (action != null) {
085            action.setAutomationItem(this); // associate action with this item
086        }
087        if (old != action) {
088            setDirtyAndFirePropertyChange("AutomationItemActionChange", old, action); // NOI18N
089        }
090    }
091
092    public Action getAction() {
093        return _action;
094    }
095
096    public String getActionName() {
097        if (getAction() != null) {
098            return getAction().getName();
099        }
100        return NONE;
101    }
102
103    public int getActionCode() {
104        if (getAction() != null) {
105            return getAction().getCode();
106        }
107        return ActionCodes.NO_ACTION;
108    }
109
110    public void doAction() {
111        if (getAction() != null) {
112            getAction().doAction();
113        }
114    }
115
116    public void setTrain(Train train) {
117        Train old = _train;
118        _train = train;
119        if (old != train) {
120            setDirtyAndFirePropertyChange("AutomationItemTrainChange", old, train); // NOI18N
121            setRouteLocation(null);
122        }
123    }
124
125    public Train getTrain() {
126        if (getAction() != null && getAction().isTrainMenuEnabled()) {
127            return _train;
128        }
129        return null;
130    }
131
132    public void setRouteLocation(RouteLocation rl) {
133        RouteLocation old = _routeLocation;
134        _routeLocation = rl;
135        if (old != rl) {
136            setDirtyAndFirePropertyChange("AutomationItemRouteLocationChange", old, rl); // NOI18N
137        }
138    }
139
140    public RouteLocation getRouteLocation() {
141        if (getAction() != null && getAction().isRouteMenuEnabled()) {
142            return _routeLocation;
143        }
144        return null;
145    }
146
147    public void setOther(Object other) {
148        if (other != null && other.getClass().equals(Automation.class)) {
149            setAutomationToRun((Automation) other);
150        }
151        else if (other != null && other.getClass().equals(AutomationItem.class)) {
152            setGotoAutomationItem((AutomationItem) other);
153        }
154        else if (other == null || other.getClass().equals(TrainSchedule.class)) {
155            setTrainSchedule((TrainSchedule) other);
156        }
157    }
158
159    /**
160     * The automation for actions, not the automation associated with this item.
161     * @param automation the automation to run
162     *
163     */
164    public void setAutomationToRun(Automation automation) {
165        Automation old = InstanceManager.getDefault(AutomationManager.class).getAutomationById(_automationIdToRun);
166        if (automation != null)
167            _automationIdToRun = automation.getId();
168        else
169            _automationIdToRun = NONE;
170        if (old != automation) {
171            setDirtyAndFirePropertyChange("AutomationItemAutomationChange", old, automation); // NOI18N
172        }
173    }
174
175    /**
176     * The automation for actions, not the automation associated with this item.
177     * 
178     * @return Automation for this action
179     */
180    public Automation getAutomationToRun() {
181        if (getAction() != null && getAction().isAutomationMenuEnabled()) {
182            return InstanceManager.getDefault(AutomationManager.class).getAutomationById(_automationIdToRun);
183        }
184        return null;
185    }
186
187    /**
188     * The automation for action GOTO, not this automation item.
189     * @param automationItem which automation item to GOTO
190     *
191     */
192    public void setGotoAutomationItem(AutomationItem automationItem) {
193        AutomationItem oldItem = null;
194        if (automationItem != null) {
195            Automation automation = InstanceManager.getDefault(AutomationManager.class).getAutomationById(automationItem.getId().split(Automation.REGEX)[0]);
196            oldItem = automation.getItemById(_gotoAutomationItemId);
197            _gotoAutomationItemId = automationItem.getId();
198        } else {
199            _gotoAutomationItemId = NONE;
200        }
201        if (oldItem != automationItem) {
202            setDirtyAndFirePropertyChange("AutomationItemAutomationChange", oldItem, automationItem); // NOI18N
203        }
204    }
205
206    /**
207     * The automationItem for actions not this item.
208     * 
209     * @return AutomationItem for GOTO
210     */
211    public AutomationItem getGotoAutomationItem() {
212        if (getAction() != null && getAction().isGotoMenuEnabled()) {
213            Automation automation = InstanceManager.getDefault(AutomationManager.class).getAutomationById(_gotoAutomationItemId.split(Automation.REGEX)[0]);
214            if (automation != null) {
215                return automation.getItemById(_gotoAutomationItemId);
216            }
217        }
218        return null;
219    }
220    
221    public void setGotoBranched(boolean branched) {
222        _gotoAutomationBranched = branched;
223    }
224    
225    public boolean isGotoBranched() {
226        return _gotoAutomationBranched;
227    }
228
229    public void setTrainSchedule(TrainSchedule trainSchedule) {
230        String old = _trainScheduleId;
231        if (trainSchedule != null) {
232            _trainScheduleId = trainSchedule.getId();
233        } else {
234            _trainScheduleId = NONE;
235        }
236        if (!old.equals(_trainScheduleId)) {
237            setDirtyAndFirePropertyChange("AutomationItemTrainScheduleChange", old, _trainScheduleId); // NOI18N
238        }
239    }
240    
241    public TrainSchedule getTrainSchedule() {
242        if (getAction() != null && getAction().isOtherMenuEnabled()) {
243            return InstanceManager.getDefault(TrainScheduleManager.class).getScheduleById(_trainScheduleId);
244        }
245        return null;
246    }
247    
248    public String getTrainScheduleId() {
249        return _trainScheduleId;
250    }
251
252    public void setMessage(String message) {
253        String old = _message;
254        _message = message;
255        if (!old.equals(message)) {
256            setDirtyAndFirePropertyChange("AutomationItemMessageChange", old, message); // NOI18N
257        }
258    }
259
260    public String getMessage() {
261        return _message;
262    }
263
264    public void setMessageFail(String message) {
265        String old = _messageFail;
266        _messageFail = message;
267        if (!old.equals(message)) {
268            setDirtyAndFirePropertyChange("AutomationItemMessageFailChange", old, message); // NOI18N
269        }
270    }
271
272    public String getMessageFail() {
273        return _messageFail;
274    }
275
276    public boolean isHaltFailureEnabled() {
277        return _haltFail;
278    }
279
280    public void setHaltFailureEnabled(boolean enable) {
281        boolean old = _haltFail;
282        _haltFail = enable;
283        if (old != enable) {
284            setDirtyAndFirePropertyChange("AutomationItemHaltFailureChange", old, enable); // NOI18N
285        }
286    }
287
288    public void setActionRunning(boolean actionRunning) {
289        boolean old = _actionRunning;
290        _actionRunning = actionRunning;
291        if (old != actionRunning) {
292            if (!actionRunning) {
293                setActionRan(true);
294            }
295            firePropertyChange("actionRunningChange", old, actionRunning); // NOI18N
296        }
297    }
298
299    public boolean isActionRunning() {
300        return _actionRunning;
301    }
302
303    public void setActionSuccessful(boolean successful) {
304        boolean old = _actionSuccessful;
305        _actionSuccessful = successful;
306        if (old != successful) {
307            setDirtyAndFirePropertyChange("actionSuccessful", old, successful); // NOI18N
308        }
309    }
310
311    public void setActionRan(boolean ran) {
312        _actionRan = ran;
313        firePropertyChange("actionRan", !ran, ran); // NOI18N
314    }
315
316    public boolean isActionRan() {
317        return _actionRan;
318    }
319
320    public boolean isActionSuccessful() {
321        return _actionSuccessful;
322    }
323
324    public String getStatus() {
325        if (isActionRunning())
326            return Bundle.getMessage("Running");
327        if (!isActionRan())
328            return NONE;
329        if (getAction() != null)
330            return isActionSuccessful() ? getAction().getActionSuccessfulString() : getAction().getActionFailedString();
331        else
332            return "unknown"; // NOI18N
333    }
334    
335    public void reset() {
336        setActionRan(false);
337        setActionSuccessful(false);
338        setGotoBranched(false);
339    }
340
341    /**
342     * Copies item.
343     * @param item The item to copy.
344     */
345    public void copyItem(AutomationItem item) {
346        setAction(getActionByCode(item.getActionCode())); // must create a new action for each item
347        setAutomationToRun(item.getAutomationToRun());
348        setGotoAutomationItem(item.getGotoAutomationItem()); //needs an adjustment to work properly
349        setTrain(item.getTrain()); // must set train before route location
350        setRouteLocation(item.getRouteLocation());
351        setSequenceId(item.getSequenceId());
352        setTrainSchedule(item.getTrainSchedule());
353        setMessage(item.getMessage());
354        setMessageFail(item.getMessageFail());
355        setHaltFailureEnabled(item.isHaltFailureEnabled());
356    }
357    
358    public static Action getActionByCode(int code) {
359        for (Action action : getActionList()) {
360            if (action.getCode() == code)
361                return action;
362        }
363        return new NoAction(); // default if code not found
364    }
365
366    /**
367     * Gets a list of all known automation actions
368     * 
369     * @return list of automation actions
370     */
371    public static List<Action> getActionList() {
372        List<Action> list = new ArrayList<>();
373        list.add(new NoAction());
374        list.add(new BuildTrainAction());
375        list.add(new BuildTrainIfSelectedAction());
376        list.add(new PrintTrainManifestAction());
377        list.add(new PrintTrainManifestIfSelectedAction());
378        list.add(new PrintTrainBuildReportAction());
379        list.add(new RunTrainAction());
380        list.add(new MoveTrainAction());
381        list.add(new TerminateTrainAction());
382        list.add(new ResetTrainAction());
383        list.add(new IsTrainEnRouteAction());
384        list.add(new WaitTrainAction());
385        list.add(new WaitTrainTerminatedAction());
386        list.add(new ActivateTrainScheduleAction());
387        list.add(new ApplyTrainScheduleAction());
388        list.add(new SelectTrainAction());
389        list.add(new DeselectTrainAction());
390        list.add(new PrintSwitchListAction());
391        list.add(new UpdateSwitchListAction());
392        list.add(new WaitSwitchListAction());
393        list.add(new GenerateSwitchListAction());
394        list.add(new GenerateSwitchListChangesAction());
395        list.add(new ResetSwitchListsAction());
396        list.add(new RunSwitchListAction());
397        list.add(new RunSwitchListChangesAction());
398        list.add(new RunAutomationAction());
399        list.add(new ResumeAutomationAction());
400        list.add(new StopAutomationAction());
401        list.add(new MessageYesNoAction());
402        list.add(new GotoAction());
403        list.add(new GotoSuccessAction());
404        list.add(new GotoFailureAction());
405        list.add(new HaltAction());
406        return list;
407    }
408
409    public static JComboBox<Action> getActionComboBox() {
410        JComboBox<Action> box = new JComboBox<>();
411        for (Action action : getActionList())
412            box.addItem(action);
413        return box;
414    }
415
416    public void dispose() {
417        setDirtyAndFirePropertyChange(DISPOSE, null, DISPOSE);
418    }
419
420    /**
421     * Construct this Entry from XML. This member has to remain synchronized
422     * with the detailed DTD in operations-trains.xml
423     *
424     * @param e Consist XML element
425     */
426    public AutomationItem(Element e) {
427        org.jdom2.Attribute a;
428        if ((a = e.getAttribute(Xml.ID)) != null) {
429            _id = a.getValue();
430        } else {
431            log.warn("no id attribute in Automation Item element when reading operations");
432        }
433        if ((a = e.getAttribute(Xml.SEQUENCE_ID)) != null) {
434            _sequenceId = Integer.parseInt(a.getValue());
435        }
436        if ((a = e.getAttribute(Xml.ACTION_CODE)) != null) {
437            setAction(getActionByCode(Integer.decode(a.getValue())));
438        }
439        if ((a = e.getAttribute(Xml.HALT_FAIL)) != null) {
440            _haltFail = a.getValue().equals(Xml.TRUE);
441        }
442        if ((a = e.getAttribute(Xml.ACTION_RAN)) != null) {
443            _actionRan = a.getValue().equals(Xml.TRUE);
444        }
445        if ((a = e.getAttribute(Xml.ACTION_SUCCESSFUL)) != null) {
446            _actionSuccessful = a.getValue().equals(Xml.TRUE);
447        }
448        if ((a = e.getAttribute(Xml.TRAIN_ID)) != null) {
449            _train = InstanceManager.getDefault(TrainManager.class).getTrainById(a.getValue());
450        }
451        if ((a = e.getAttribute(Xml.ROUTE_LOCATION_ID)) != null && getTrain() != null) {
452            _routeLocation = getTrain().getRoute().getLocationById(a.getValue());
453        }
454        if ((a = e.getAttribute(Xml.AUTOMATION_ID)) != null) {
455            // in the process of loading automations, so we can't get them now, save id and get later.
456            _automationIdToRun = a.getValue();
457        }
458        if ((a = e.getAttribute(Xml.GOTO_AUTOMATION_ID)) != null) {
459            // in the process of loading automations, so we can't get them now, save id and get later.
460            _gotoAutomationItemId = a.getValue();
461        }
462        if ((a = e.getAttribute(Xml.GOTO_AUTOMATION_BRANCHED)) != null) {
463            _gotoAutomationBranched = a.getValue().equals(Xml.TRUE);
464        }
465        if ((a = e.getAttribute(Xml.TRAIN_SCHEDULE_ID)) != null) {
466            _trainScheduleId = a.getValue();
467        }
468        Element eMessages = e.getChild(Xml.MESSAGES);
469        if (eMessages != null) {
470            Element eMessageOk = eMessages.getChild(Xml.MESSAGE_OK);
471            if (eMessageOk != null && (a = eMessageOk.getAttribute(Xml.MESSAGE)) != null) {
472                _message = a.getValue();
473            }
474            Element eMessageFail = eMessages.getChild(Xml.MESSAGE_FAIL);
475            if (eMessageFail != null && (a = eMessageFail.getAttribute(Xml.MESSAGE)) != null) {
476                _messageFail = a.getValue();
477            }
478        }
479    }
480
481    /**
482     * Create an XML element to represent this Entry. This member has to remain
483     * synchronized with the detailed DTD in operations-trains.dtd.
484     *
485     * @return Contents in a JDOM Element
486     */
487    public Element store() {
488        Element e = new Element(Xml.ITEM);
489        e.setAttribute(Xml.ID, getId());
490        e.setAttribute(Xml.SEQUENCE_ID, Integer.toString(getSequenceId()));
491        e.setAttribute(Xml.NAME, getActionName());
492        e.setAttribute(Xml.ACTION_CODE, "0x" + Integer.toHexString(getActionCode())); // NOI18N
493        e.setAttribute(Xml.HALT_FAIL, isHaltFailureEnabled() ? Xml.TRUE : Xml.FALSE);
494        e.setAttribute(Xml.ACTION_RAN, isActionRan() ? Xml.TRUE : Xml.FALSE);
495        e.setAttribute(Xml.ACTION_SUCCESSFUL, isActionSuccessful() ? Xml.TRUE : Xml.FALSE);
496        if (getTrain() != null) {
497            e.setAttribute(Xml.TRAIN_ID, getTrain().getId());
498            if (getRouteLocation() != null) {
499                e.setAttribute(Xml.ROUTE_LOCATION_ID, getRouteLocation().getId());
500            }
501        }
502        if (getAutomationToRun() != null) {
503            e.setAttribute(Xml.AUTOMATION_ID, getAutomationToRun().getId());
504        }
505        if (getGotoAutomationItem() != null) {
506            e.setAttribute(Xml.GOTO_AUTOMATION_ID, getGotoAutomationItem().getId());
507            e.setAttribute(Xml.GOTO_AUTOMATION_BRANCHED, isGotoBranched() ? Xml.TRUE : Xml.FALSE);
508        }
509        if (getTrainSchedule() != null) {
510            e.setAttribute(Xml.TRAIN_SCHEDULE_ID, getTrainSchedule().getId());
511        }
512        if (!getMessage().equals(NONE) || !getMessageFail().equals(NONE)) {
513            Element eMessages = new Element(Xml.MESSAGES);
514            e.addContent(eMessages);
515            Element eMessageOk = new Element(Xml.MESSAGE_OK);
516            eMessageOk.setAttribute(Xml.MESSAGE, getMessage());
517            Element eMessageFail = new Element(Xml.MESSAGE_FAIL);
518            eMessageFail.setAttribute(Xml.MESSAGE, getMessageFail());
519            eMessages.addContent(eMessageOk);
520            eMessages.addContent(eMessageFail);
521        }
522        return e;
523    }
524
525    @Override
526    public void propertyChange(java.beans.PropertyChangeEvent e) {
527        if (Control.SHOW_PROPERTY) {
528            log.debug("AutomationItem id ({}) sees property change: ({}) old: ({}) new: ({})",
529                    getId(), e.getPropertyName(), e.getOldValue(), e.getNewValue()); // NOI18N
530        }
531    }
532
533    protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) {
534        // set dirty
535        InstanceManager.getDefault(TrainManagerXml.class).setDirty(true);
536        firePropertyChange(p, old, n);
537    }
538
539    private final static Logger log = LoggerFactory.getLogger(AutomationItem.class);
540
541}