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
042        tc.sendDCCppMessage(DCCppMessage.makeTurnoutIDsMsg(), this);
043    }
044
045    /**
046     * {@inheritDoc}
047     */
048    @Override
049    @Nonnull
050    public DCCppSystemConnectionMemo getMemo() {
051        return (DCCppSystemConnectionMemo) memo;
052    }
053
054    // DCCpp-specific methods
055
056    /**
057     * {@inheritDoc}
058     */
059    @Nonnull
060    @Override
061    protected Turnout createNewTurnout(@Nonnull String systemName, String userName) throws IllegalArgumentException {
062        // check if the output bit is available
063        int bitNum = getBitFromSystemName(systemName);
064        if (bitNum < 0) {
065            throw new IllegalArgumentException("Cannot get Bit from System Name " + systemName);
066        }
067        // make the new Turnout object
068        Turnout t = new DCCppTurnout(getSystemPrefix(), bitNum, tc);
069        t.setUserName(userName);
070        return t;
071    }
072
073    /**
074     * {@inheritDoc}
075     * Listen for turnouts, creating them as needed.
076     */
077    @Override
078    public void message(DCCppReply l) {
079        if (l.isTurnoutReply()) {
080            log.debug("received Turnout Reply message: '{}'", l);
081            // parse message type
082            int addr = l.getTOIDInt();
083            if (addr >= 0) {
084                // check to see if the address has been operated before
085                // continuing.
086                log.debug("message has address: {}", addr);
087                // reach here for switch command; make sure we know 
088                // about this one
089                String s = getSystemNamePrefix() + addr;
090                DCCppTurnout found = (DCCppTurnout) getBySystemName(s);
091                if ( found == null) {
092                    // need to create a new one, set some attributes, 
093                    //  and send the message on to the newly created object.
094                    DCCppTurnout t = (DCCppTurnout) provideTurnout(s);
095                    t.setFeedbackMode(Turnout.MONITORING);
096                    t.initmessage(l);
097                } else {
098                    // The turnout exists, forward this message to the 
099                    // turnout
100                    found.message(l);
101                }
102            }
103        } else if (l.isOutputReply()) {
104            log.debug("received Output Reply message: '{}'", l);
105            // parse message type
106            int addr = l.getOutputNumInt();
107            if (addr >= 0) {
108                // check to see if the address has been operated before
109                // continuing.
110                log.debug("message has address: {}", addr);
111                // reach here for switch command; make sure we know 
112                // about this one
113                String s = getSystemNamePrefix() + addr;
114                DCCppTurnout found = (DCCppTurnout) getBySystemName(s);
115                if (found == null) {
116                    // need to create a new one, and send the message on 
117                    // to the newly created object.
118                    DCCppTurnout t = (DCCppTurnout) provideTurnout(s);
119                    t.setFeedbackMode(Turnout.EXACT);
120                    t.initmessage(l);
121                } else {
122                    // The turnout exists, forward this message to the 
123                    // turnout
124                    found.message(l);
125                }
126            }
127        } else if (l.isTurnoutIDsReply()) {
128            log.debug("received Turnout ID List message: '{}'", l);
129            ArrayList<Integer> ids = l.getTurnoutIDList();
130            for (Integer id : ids) { //request details for each id included in this message
131                tc.sendDCCppMessage(DCCppMessage.makeTurnoutIDMsg(id), this);
132                tc.sendDCCppMessage(DCCppMessage.makeTurnoutImplMsg(id), this);
133            }
134        } else if (l.isTurnoutIDReply()) {
135            log.debug("received Turnout ID Detail message: '{}'", l);
136            // parse message type
137            int addr = l.getTOIDInt();
138            if (addr >= 0) {
139                log.debug("message has address: {}", addr);
140                String s = getSystemNamePrefix() + addr;
141                DCCppTurnout found = (DCCppTurnout) getBySystemName(s);
142                if ( found == null) { //create new turnout
143                    DCCppTurnout t = (DCCppTurnout) provideTurnout(s);
144                    t.setFeedbackMode(Turnout.MONITORING);
145                    if (!l.getTurnoutDescString().isEmpty()) {
146                        t.setUserName(l.getTurnoutDescString()); //add username if available
147                    }
148                    t.initmessage(l); //forward message to turnout
149                } else { // The turnout already exists                    
150                    if (!l.getTurnoutDescString().isEmpty() && found.getUserName()==null) {
151                        found.setUserName(l.getTurnoutDescString()); //set userName if needed and available
152                    }
153                    found.message(l); //forward message to turnout
154                }
155            }
156        }
157    }
158    
159    /**
160     * Get text to be used for the Turnout.CLOSED state in user communication.
161     * Allows text other than "CLOSED" to be use with certain hardware system to
162     * represent the Turnout.CLOSED state.
163     */
164    @Override
165    @Nonnull
166    public String getClosedText() {
167        return Bundle.getMessage("TurnoutStateClosed");
168    }
169
170    /**
171     * Get text to be used for the Turnout.THROWN state in user communication.
172     * Allows text other than "THROWN" to be use with certain hardware system to
173     * represent the Turnout.THROWN state.
174     */
175    @Override
176    @Nonnull
177    public String getThrownText() {
178        return Bundle.getMessage("TurnoutStateThrown");
179    }
180
181    /**
182     * Listen for the outgoing messages (to the command station)
183     */
184    @Override
185    public void message(DCCppMessage l) {
186    }
187
188    // Handle message timeout notification
189    // If the message still has retries available, reduce retries and send it back to the traffic controller.
190    @Override
191    public void notifyTimeout(DCCppMessage msg) {
192        log.debug("Notified of timeout on message '{}' , {} retries available.", msg, msg.getRetries());
193        if (msg.getRetries() > 0) {
194            msg.setRetries(msg.getRetries() - 1);
195            tc.sendDCCppMessage(msg, this);
196        }        
197    }
198
199    /** {@inheritDoc} */
200    @Override
201    public boolean allowMultipleAdditions(@Nonnull String systemName) {
202        return true;
203    }
204
205    /**
206     * {@inheritDoc}
207     */
208    @Override
209    public NameValidity validSystemNameFormat(@Nonnull String systemName) {
210        return (getBitFromSystemName(systemName) != -1) ? NameValidity.VALID : NameValidity.INVALID;
211    }
212
213    /**
214     * {@inheritDoc}
215     */
216    @Override
217    @Nonnull
218    public String validateSystemNameFormat(@Nonnull String systemName, @Nonnull Locale locale) {
219        return validateIntegerSystemNameFormat(systemName, 0, MAX_TURNOUT_ADDRESS, locale);
220    }
221
222    /**
223     * Get the bit address from the system name.
224     *
225     * @param systemName a valid Turnout System Name
226     * @return the turnout number extracted from the system name
227     */
228    public int getBitFromSystemName(String systemName) {
229        try {
230            validateSystemNameFormat(systemName, Locale.getDefault());
231        } catch (IllegalArgumentException ex) {
232            return -1;
233        }
234        return Integer.parseInt(systemName.substring(getSystemNamePrefix().length()));
235    }
236
237    /** {@inheritDoc} */
238    @Override
239    public String getEntryToolTip() {
240        return Bundle.getMessage("AddOutputEntryToolTip");
241    }
242
243    private final static Logger log = LoggerFactory.getLogger(DCCppTurnoutManager.class);
244
245}