001package jmri.implementation;
002
003import java.util.*;
004import javax.annotation.Nonnull;
005import jmri.NamedBeanHandle;
006import jmri.Turnout;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010/**
011 * SignalMast implemented via Turnout objects.
012 * <p>
013 * A SignalMast that is built up using turnouts to control a specific
014 * appearance. System name specifies the creation information:
015 * <pre>
016 * IF$tsm:basic:one-searchlight(IT1)(IT2)
017 * </pre> The name is a colon-separated series of terms:
018 * <ul>
019 * <li>IF$tsm - defines signal masts of this type
020 * <li>basic - name of the signaling system
021 * <li>one-searchlight - name of the particular aspect map
022 * <li>(IT1)(IT2) - colon-separated list of names for Turnouts
023 * </ul>
024 *
025 * @author Bob Jacobsen Copyright (C) 2009, 2014
026 */
027public class TurnoutSignalMast extends AbstractSignalMast {
028
029    public TurnoutSignalMast(String systemName, String userName) {
030        super(systemName, userName);
031        configureFromName(systemName);
032    }
033
034    public TurnoutSignalMast(String systemName) {
035        super(systemName);
036        configureFromName(systemName);
037    }
038
039    private static final String THE_MAST_TYPE = "IF$tsm";
040
041    private void configureFromName(String systemName) {
042        // split out the basic information
043        String[] parts = systemName.split(":");
044        if (parts.length < 3) {
045            log.error("SignalMast system name needs at least three parts: {}", systemName);
046            throw new IllegalArgumentException("System name needs at least three parts: " + systemName);
047        }
048        if (!parts[0].equals(THE_MAST_TYPE)) {
049            log.warn("SignalMast system name should start with {} but is {}", THE_MAST_TYPE, systemName);
050        }
051        String system = parts[1];
052        String mast = parts[2];
053
054        mast = mast.substring(0, mast.indexOf("("));
055        setMastType(mast);
056
057        String tmp = parts[2].substring(parts[2].indexOf("($") + 2, parts[2].indexOf(")"));
058        try {
059            int autoNumber = Integer.parseInt(tmp);
060            if (autoNumber > getLastRef()) {
061                setLastRef(autoNumber);
062            }
063        } catch (NumberFormatException e) {
064            log.warn("Auto generated SystemName {} is not in the correct format", systemName);
065        }
066
067        configureSignalSystemDefinition(system);
068        configureAspectTable(system, mast);
069    }
070
071    @Override
072    public void setAspect(@Nonnull String aspect) {
073        // check it's a choice
074        if (!map.checkAspect(aspect)) {
075            // not a valid aspect
076            log.warn("attempting to set invalid aspect: {} on mast: {}", aspect, getDisplayName());
077            throw new IllegalArgumentException("attempting to set invalid aspect: " + aspect + " on mast: " + getDisplayName());
078        } else if (disabledAspects.contains(aspect)) {
079            log.warn("attempting to set an aspect that has been disabled: {} on mast: {}", aspect, getDisplayName());
080            throw new IllegalArgumentException("attempting to set an aspect that has been disabled: " + aspect + " on mast: " + getDisplayName());
081        }
082
083
084        if (getLit()) { // If the signalmast is lit, then send the commands to change the aspect.
085
086            // reset all states before setting this one, including this one
087            if (resetPreviousStates) {
088                // Clear all the current states, this will result in the signalmast going blank for a very short time.
089                for (Map.Entry<String, TurnoutAspect> entry : turnouts.entrySet()) {
090                    String appearance = entry.getKey();
091                    TurnoutAspect aspt = entry.getValue();
092                    if (!isAspectDisabled(appearance)) {
093                        int setState = Turnout.CLOSED;
094                        if (aspt.getTurnoutState() == Turnout.CLOSED) {
095                            setState = Turnout.THROWN;
096                        }
097                        if (aspt.getTurnout() != null ) {
098                            if (aspt.getTurnout().getKnownState() != setState) {
099                                aspt.getTurnout().setCommandedState(setState);
100                            }
101                        } else {
102                            log.error("Trying to reset \"{}\" on signal mast \"{}\" which has not been configured", appearance, getDisplayName());
103                        }
104                    }
105                }
106            }
107
108            // set the finel state if possible
109            if (turnouts.get(aspect) != null && turnouts.get(aspect).getTurnout() != null) {
110                Turnout turnToSet = turnouts.get(aspect).getTurnout();
111                int stateToSet = turnouts.get(aspect).getTurnoutState();
112                turnToSet.setCommandedState(stateToSet);
113            } else {
114                log.error("Trying to set \"{}\" on signal mast \"{}\" which has not been configured", aspect, getDisplayName());
115            }
116
117        } else if (log.isDebugEnabled()) {
118            log.debug("Mast set to unlit, will not send aspect change to hardware");
119        }
120        super.setAspect(aspect);
121    }
122
123    private TurnoutAspect unLit = null;
124
125    public void setUnLitTurnout(String turnoutName, int turnoutState) {
126        unLit = new TurnoutAspect(turnoutName, turnoutState);
127    }
128
129    public String getUnLitTurnoutName() {
130        if (unLit != null) {
131            return unLit.getTurnoutName();
132        }
133        return null;
134    }
135
136    public Turnout getUnLitTurnout() {
137        if (unLit != null) {
138            return unLit.getTurnout();
139        }
140        return null;
141    }
142
143    public int getUnLitTurnoutState() {
144        if (unLit != null) {
145            return unLit.getTurnoutState();
146        }
147        return -1;
148    }
149
150    @Override
151    public void setLit(boolean newLit) {
152        if (!allowUnLit() || newLit == getLit()) {
153            return;
154        }
155        super.setLit(newLit);
156        if (newLit) {
157            // This will force the signalmast to send out the commands to set the aspect again.
158            String litAspect = getAspect();
159            if (litAspect != null ) {
160                setAspect(litAspect);
161            }
162        } else {
163            if (unLit != null) {
164                // there is a specific unlit output defined
165                Turnout t = unLit.getTurnout();
166                if (t != null && t.getKnownState() != getUnLitTurnoutState()) {
167                    t.setCommandedState(getUnLitTurnoutState());
168                }
169            } else {
170                // turn everything off
171                for (TurnoutAspect tAspect : turnouts.values()) {
172                    int setState = Turnout.CLOSED;
173                    if (tAspect.getTurnoutState() == Turnout.CLOSED) {
174                        setState = Turnout.THROWN;
175                    }
176                    if (tAspect.getTurnout().getKnownState() != setState) {
177                        tAspect.getTurnout().setCommandedState(setState);
178                    }
179                }
180            }
181        }
182    }
183
184    public String getTurnoutName(String appearance) {
185        TurnoutAspect tAspect = turnouts.get(appearance);
186        if (tAspect != null) {
187            return tAspect.getTurnoutName();
188        }
189        return "";
190    }
191
192    public int getTurnoutState(String appearance) {
193        TurnoutAspect tAspect = turnouts.get(appearance);
194        if (tAspect != null) {
195            return tAspect.getTurnoutState();
196        }
197        return -1;
198    }
199
200    public void setTurnout(String appearance, String turn, int state) {
201        if (turnouts.containsKey(appearance)) {
202            log.debug("Appearance {} is already defined so will override", appearance);
203            turnouts.remove(appearance);
204        }
205        turnouts.put(appearance, new TurnoutAspect(turn, state));
206    }
207
208    HashMap<String, TurnoutAspect> turnouts = new HashMap<>();
209
210    private boolean resetPreviousStates = false;
211
212    /**
213     * If the signal mast driver requires the previous state to be cleared down
214     * before the next state is set.
215     *
216     * @param boo true if prior states should be cleared; false otherwise
217     */
218    public void resetPreviousStates(boolean boo) {
219        resetPreviousStates = boo;
220    }
221
222    public boolean resetPreviousStates() {
223        return resetPreviousStates;
224    }
225
226    static class TurnoutAspect {
227
228        NamedBeanHandle<Turnout> namedTurnout;
229        int state;
230
231        TurnoutAspect(String turnoutName, int turnoutState) {
232            if (turnoutName != null && !turnoutName.equals("")) {
233                state = turnoutState;
234                Turnout turn = jmri.InstanceManager.turnoutManagerInstance().getTurnout(turnoutName);
235                if (turn == null) {
236                    log.error("TurnoutAspect couldn't locate turnout {}", turnoutName);
237                    return;
238                }
239                namedTurnout = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(turnoutName, turn);
240            }
241        }
242
243        Turnout getTurnout() {
244            if (namedTurnout == null) {
245                return null;
246            }
247            return namedTurnout.getBean();
248        }
249
250        String getTurnoutName() {
251            if (namedTurnout == null) {
252                return null;
253            }
254            return namedTurnout.getName();
255        }
256
257        int getTurnoutState() {
258            return state;
259        }
260    }
261
262    boolean isTurnoutUsed(Turnout t) {
263        for (TurnoutAspect ta : turnouts.values()) {
264            if (t.equals(ta.getTurnout())) {
265                return true;
266            }
267        }
268        return t.equals(getUnLitTurnout());
269    }
270
271    public List<NamedBeanHandle<Turnout>> getHeadsUsed() {
272        return new ArrayList<>();
273    }
274
275    /**
276     *
277     * @param newVal for ordinal of all TurnoutSignalMasts in use
278     */
279    protected static void setLastRef(int newVal) {
280        lastRef = newVal;
281    }
282
283    /**
284     * @return highest ordinal of all TurnoutSignalMasts in use
285     */
286    public static int getLastRef() {
287        return lastRef;
288    }
289
290    /**
291     * Ordinal of all TurnoutSignalMasts to create unique system name.
292     */
293    protected static volatile int lastRef = 0;
294    // TODO narrow access to static, once jmri/jmrit/beantable/signalmast/TurnoutSignalMastAddPane uses setLastRef(n)
295    //private static volatile int lastRef = 0;
296
297    @Override
298    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
299        if ("CanDelete".equals(evt.getPropertyName())) { // NOI18N
300            if (evt.getOldValue() instanceof Turnout) {
301                if (isTurnoutUsed((Turnout) evt.getOldValue())) {
302                    java.beans.PropertyChangeEvent e = new java.beans.PropertyChangeEvent(this, "DoNotDelete", null, null);
303                    throw new java.beans.PropertyVetoException(Bundle.getMessage("InUseTurnoutSignalMastVeto", getDisplayName()), e); // NOI18N
304                }
305            }
306        }
307    }
308
309    private final static Logger log = LoggerFactory.getLogger(TurnoutSignalMast.class);
310
311}