001package jmri.jmrix.dccpp;
002
003import static jmri.jmrix.dccpp.DCCppConstants.MAX_TURNOUT_ADDRESS;
004
005import java.util.Locale;
006import java.util.ArrayList;
007
008import javax.annotation.Nonnull;
009import jmri.Turnout;
010import org.slf4j.Logger;
011import org.slf4j.LoggerFactory;
012
013/**
014 * Implement TurnoutManager for DCC++ systems.
015 * <p>
016 * System names are "DxppTnnn", where Dx is the system prefix and nnn is the turnout number without padding.
017 *
018 * @author Bob Jacobsen Copyright (C) 2001
019 * @author Paul Bender Copyright (C) 2003-2010
020 * @author Mark Underwood Copyright (C) 2015
021 */
022public class DCCppTurnoutManager extends jmri.managers.AbstractTurnoutManager implements DCCppListener {
023
024    protected DCCppTrafficController tc = null;
025
026    /**
027     * Create a new DCC++ TurnoutManager.
028     * Has to register for DCC++ events.
029     *
030     * @param memo the supporting system connection memo
031     */
032    public DCCppTurnoutManager(DCCppSystemConnectionMemo memo) {
033        super(memo);
034        tc = memo.getDCCppTrafficController();
035        // set up listener
036        tc.addDCCppListener(DCCppInterface.FEEDBACK, this);
037        // request list of turnouts
038        tc.sendDCCppMessage(DCCppMessage.makeTurnoutListMsg(), this);
039        // request list of outputs
040        tc.sendDCCppMessage(DCCppMessage.makeOutputListMsg(), this);
041        // request list of Turnout IDs if needed
042        if (tc.getCommandStation().isTurnoutIDsMessageRequired()) {
043            tc.sendDCCppMessage(DCCppMessage.makeTurnoutIDsMsg(), this);
044        }
045    }
046
047    /**
048     * {@inheritDoc}
049     */
050    @Override
051    @Nonnull
052    public DCCppSystemConnectionMemo getMemo() {
053        return (DCCppSystemConnectionMemo) memo;
054    }
055
056    // DCCpp-specific methods
057
058    /**
059     * {@inheritDoc}
060     */
061    @Nonnull
062    @Override
063    protected Turnout createNewTurnout(@Nonnull String systemName, String userName) throws IllegalArgumentException {
064        // check if the output bit is available
065        int bitNum = getBitFromSystemName(systemName);
066        if (bitNum < 0) {
067            throw new IllegalArgumentException("Cannot get Bit from System Name " + systemName);
068        }
069        // make the new Turnout object
070        Turnout t = new DCCppTurnout(getSystemPrefix(), bitNum, tc);
071        t.setUserName(userName);
072        return t;
073    }
074
075    /**
076     * {@inheritDoc}
077     * Listen for turnouts, creating them as needed.
078     */
079    @Override
080    public void message(DCCppReply l) {
081        if (l.isTurnoutReply()) {
082            log.debug("received Turnout Reply message: '{}'", l);
083            // parse message type
084            int addr = l.getTOIDInt();
085            if (addr >= 0) {
086                // check to see if the address has been operated before
087                // continuing.
088                log.debug("message has address: {}", addr);
089                // reach here for switch command; make sure we know 
090                // about this one
091                String s = getSystemNamePrefix() + addr;
092                DCCppTurnout found = (DCCppTurnout) getBySystemName(s);
093                if ( found == null) {
094                    // need to create a new one, set some attributes, 
095                    //  and send the message on to the newly created object.
096                    DCCppTurnout t = (DCCppTurnout) provideTurnout(s);
097                    t.setFeedbackMode(Turnout.MONITORING);
098                    t.initmessage(l);
099                } else {
100                    // The turnout exists, forward this message to the 
101                    // turnout
102                    found.message(l);
103                }
104            }
105        } else if (l.isOutputReply()) {
106            log.debug("received Output Reply message: '{}'", l);
107            // parse message type
108            int addr = l.getOutputNumInt();
109            if (addr >= 0) {
110                // check to see if the address has been operated before
111                // continuing.
112                log.debug("message has address: {}", addr);
113                // reach here for switch command; make sure we know 
114                // about this one
115                String s = getSystemNamePrefix() + addr;
116                DCCppTurnout found = (DCCppTurnout) getBySystemName(s);
117                if (found == null) {
118                    // need to create a new one, and send the message on 
119                    // to the newly created object.
120                    DCCppTurnout t = (DCCppTurnout) provideTurnout(s);
121                    t.setFeedbackMode(Turnout.EXACT);
122                    t.initmessage(l);
123                } else {
124                    // The turnout exists, forward this message to the 
125                    // turnout
126                    found.message(l);
127                }
128            }
129        } else if (l.isTurnoutIDsReply()) {
130            log.debug("received Turnout ID List message: '{}'", l);
131            ArrayList<Integer> ids = l.getTurnoutIDList();
132            for (Integer id : ids) { //request details for each id included in this message
133                tc.sendDCCppMessage(DCCppMessage.makeTurnoutIDMsg(id), this);
134                tc.sendDCCppMessage(DCCppMessage.makeTurnoutImplMsg(id), this);
135            }
136        } else if (l.isTurnoutIDReply()) {
137            log.debug("received Turnout ID Detail message: '{}'", l);
138            // parse message type
139            int addr = l.getTOIDInt();
140            if (addr >= 0) {
141                log.debug("message has address: {}", addr);
142                String s = getSystemNamePrefix() + addr;
143                DCCppTurnout found = (DCCppTurnout) getBySystemName(s);
144                if ( found == null) { //create new turnout
145                    DCCppTurnout t = (DCCppTurnout) provideTurnout(s);
146                    t.setFeedbackMode(Turnout.MONITORING);
147                    if (!l.getTurnoutDescString().isEmpty()) {
148                        t.setUserName(l.getTurnoutDescString()); //add username if available
149                    }
150                    t.initmessage(l); //forward message to turnout
151                } else { // The turnout already exists                    
152                    if (!l.getTurnoutDescString().isEmpty() && found.getUserName()==null) {
153                        found.setUserName(l.getTurnoutDescString()); //set userName if needed and available
154                    }
155                    found.message(l); //forward message to turnout
156                }
157            }
158        }
159    }
160    
161    /**
162     * Get text to be used for the Turnout.CLOSED state in user communication.
163     * Allows text other than "CLOSED" to be use with certain hardware system to
164     * represent the Turnout.CLOSED state.
165     */
166    @Override
167    @Nonnull
168    public String getClosedText() {
169        return Bundle.getMessage("TurnoutStateClosed");
170    }
171
172    /**
173     * Get text to be used for the Turnout.THROWN state in user communication.
174     * Allows text other than "THROWN" to be use with certain hardware system to
175     * represent the Turnout.THROWN state.
176     */
177    @Override
178    @Nonnull
179    public String getThrownText() {
180        return Bundle.getMessage("TurnoutStateThrown");
181    }
182
183    /**
184     * Listen for the outgoing messages (to the command station)
185     */
186    @Override
187    public void message(DCCppMessage l) {
188    }
189
190    // Handle message timeout notification
191    // If the message still has retries available, reduce retries and send it back to the traffic controller.
192    @Override
193    public void notifyTimeout(DCCppMessage msg) {
194        log.debug("Notified of timeout on message '{}' , {} retries available.", msg, msg.getRetries());
195        if (msg.getRetries() > 0) {
196            msg.setRetries(msg.getRetries() - 1);
197            tc.sendDCCppMessage(msg, this);
198        }        
199    }
200
201    /** {@inheritDoc} */
202    @Override
203    public boolean allowMultipleAdditions(@Nonnull String systemName) {
204        return true;
205    }
206
207    /**
208     * {@inheritDoc}
209     */
210    @Override
211    public NameValidity validSystemNameFormat(@Nonnull String systemName) {
212        return (getBitFromSystemName(systemName) != -1) ? NameValidity.VALID : NameValidity.INVALID;
213    }
214
215    /**
216     * {@inheritDoc}
217     */
218    @Override
219    @Nonnull
220    public String validateSystemNameFormat(@Nonnull String systemName, @Nonnull Locale locale) {
221        return validateIntegerSystemNameFormat(systemName, 0, MAX_TURNOUT_ADDRESS, locale);
222    }
223
224    /**
225     * Get the bit address from the system name.
226     *
227     * @param systemName a valid Turnout System Name
228     * @return the turnout number extracted from the system name
229     */
230    public int getBitFromSystemName(String systemName) {
231        try {
232            validateSystemNameFormat(systemName, Locale.getDefault());
233        } catch (IllegalArgumentException ex) {
234            return -1;
235        }
236        return Integer.parseInt(systemName.substring(getSystemNamePrefix().length()));
237    }
238
239    /** {@inheritDoc} */
240    @Override
241    public String getEntryToolTip() {
242        return Bundle.getMessage("AddOutputEntryToolTip");
243    }
244
245    private final static Logger log = LoggerFactory.getLogger(DCCppTurnoutManager.class);
246
247}