001package jmri.jmrix.bidib;
002
003import java.util.ArrayList;
004import java.util.Locale;
005import javax.annotation.Nonnull;
006import jmri.JmriException;
007import jmri.Light;
008import jmri.managers.AbstractLightManager;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012/**
013 * Implement LightManager for BiDiB systems.
014 *
015 * @author Paul Bender Copyright (C) 2008
016 * @author Eckart Meyer Copyright (C) 2019
017 */
018public class BiDiBLightManager extends AbstractLightManager {
019
020    private BiDiBTrafficController tc = null;
021
022    // Whether we accumulate partially loaded turnouts in pendingLights.
023    private boolean isLoading = false;
024    // Lights that are being loaded from XML.
025    private final ArrayList<BiDiBLight> pendingLights = new ArrayList<>();
026
027
028    public BiDiBLightManager(BiDiBSystemConnectionMemo memo) {
029        super(memo);
030        this.tc = memo.getBiDiBTrafficController();
031    }
032
033    /**
034     * {@inheritDoc}
035     */
036    @Override
037    public BiDiBSystemConnectionMemo getMemo() {
038        return (BiDiBSystemConnectionMemo) memo;
039    }
040
041    /**
042     * Create a new Light based on the system name.
043     * Assumes calling method has checked that a Light with this
044     * system name does not already exist.
045     *
046     * @return null if the system name is not in a valid format.
047     */
048    @Override
049    public Light createNewLight(String systemName, String userName) {
050        log.trace("createNewLight {} - {}", systemName, userName);
051
052        // first, check validity
053        try {
054            validateSystemNameFormat(systemName);
055        } catch (IllegalArgumentException e) {
056            log.error("Error validating", e);
057            throw e;
058        }
059        // OK, make
060        BiDiBLight lgt = new BiDiBLight(systemName, this);
061        lgt.setUserName(userName);
062
063        synchronized (pendingLights) {
064            if (isLoading) {
065                pendingLights.add(lgt);
066            } else {
067                lgt.finishLoad();
068            }
069        }
070
071        return lgt;
072    }
073
074    /**
075     * This function is invoked before an XML load is started. We defer initialization of the
076     * newly created turnouts until finishLoad because the feedback type might be changing as we
077     * are parsing the XML.
078     */
079    public void startLoad() {
080        log.debug("Light manager : start load");
081        synchronized (pendingLights) {
082            isLoading = true;
083        }
084    }
085
086    /**
087     * This function is invoked after the XML load is complete and all Sensors are instantiated
088     * and their type is read in. We use this hook to finalize the construction of the
089     * objects whose instantiation was deferred until the feedback type was known.
090     */
091    public void finishLoad() {
092        log.info("Light manager : finish load");
093        synchronized (pendingLights) {
094            pendingLights.forEach((s) -> {
095                s.finishLoad();
096            });
097            pendingLights.clear();
098            isLoading = false;
099        }
100    }
101
102    /**
103     * {@inheritDoc}
104     */
105    @Override
106    public String createSystemName(@Nonnull String curAddress, @Nonnull String prefix) throws JmriException { //TODO some validation? Throw exception then? - see parent class
107        log.trace("createSystemName from {} - {}", curAddress, prefix);
108        try {
109            int i = 1;
110            int curNum = Integer.parseInt(curAddress);
111            for (Light lgt : getNamedBeanSet()) {
112                //log.trace("turnout: {}/{} {}", i, curNum, lgt.getSystemName());
113                if (i++ == curNum) {
114                    return lgt.getSystemName();
115                }
116            }
117        } catch (java.lang.NumberFormatException ex) {
118            throw new JmriException("Hardware Address passed "+curAddress+" should be a number");
119        }
120        return prefix + typeLetter() + curAddress;
121    }
122    
123    /**
124     * {@inheritDoc}
125     */
126    @Override
127    public String validateSystemNameFormat(String name, Locale locale) {
128        log.trace("validateSystemNameFormat: name: {}, typeLetter: {}", name, typeLetter());
129        validateSystemNamePrefix(name, locale);
130        //validateAddressFormat(name.substring(getSystemNamePrefix().length()));
131        if (!BiDiBAddress.isValidSystemNameFormat(name, typeLetter(), getMemo())) {
132            throw new jmri.NamedBean.BadSystemNameException(Locale.getDefault(), "InvalidSystemName",name);
133        }
134        return name;
135    }
136    
137    /**
138     * {@inheritDoc}
139     */
140    @Override
141    public NameValidity validSystemNameFormat(String systemName) {
142        if (systemName.length() <= getSystemPrefix().length() ) {
143            return NameValidity.INVALID;
144        }
145//        try {
146//            validateAddressFormat(addr);
147//        } catch (IllegalArgumentException e) {
148//            return NameValidity.INVALID;
149//        }
150        return NameValidity.VALID;
151    }
152
153    /**
154     * Validate system name for configuration.
155     *
156     * @param systemName system name to validate
157     * @return 'true' if system name has a valid meaning in current configuration, else returns
158     * 'false'. For now, this method always returns 'true'; it is needed for the
159     * Abstract Light class.
160     */
161    @Override
162    public boolean validSystemNameConfig(String systemName) {
163        return (true);
164    }
165
166    /**
167     * Determine if it is possible to add a range of lights in
168     * numerical order eg 11 thru 18, primarily used to enable/disable the Add
169     * range checkbox in the Add Light pane.
170     * 
171     * @param systemName system name to check for (not used so far)
172     * @return true if multiple additions are possible. For now, this is always the case.
173     */
174    @Override
175    public boolean allowMultipleAdditions(String systemName) {
176        return true;
177    }
178
179    /**
180     * {@inheritDoc}
181     */
182    @Override
183    public String getEntryToolTip() {
184        return Bundle.getMessage("AddOutputEntryToolTip");
185    }
186    
187    /**
188     * Request config from all LC ports. The resulting config messages are processed by the Message Listeners of the Light and Sensor instances.
189     */
190    public void configAll() {
191        log.trace("configAll tc: {}", tc);
192        log.debug("LightManager config must be called after SensorManager config. If this changes in JMRI, sensor ports won't be checked!");
193        tc.allPortConfigX();
194    }
195    
196    private final static Logger log = LoggerFactory.getLogger(BiDiBLightManager.class);
197
198}