001package jmri.implementation;
002
003import java.util.Arrays;
004
005import javax.annotation.CheckForNull;
006
007import jmri.NamedBeanHandle;
008import jmri.Turnout;
009
010/**
011 * Implement SignalHead for the MERG Signal Driver 2.
012 * <p>
013 * The Signal Driver, runs off of the output of a steady State Accessory
014 * decoder. Can be configured to run 2, 3 or 4 Aspect signals. With 2 or 3
015 * aspect signals it may have a feather included.
016 * <p>
017 * The driver is designed to be used with UK based signals.
018 * <p>
019 * The class assigns turnout positions for RED, YELLOW, GREEN and Double Yellow
020 * aspects. THE SD2 does not support flashing double yellow aspects on turnouts, so
021 * an alternative method is required to do this, as per the MERG SD2
022 * documentation.
023 * <p>
024 * As there is no Double Yellow asigned within JMRI, we use the Lunar instead.
025 * <p>
026 * For more info on the signals, see
027 * <a href="http://www.merg.info">http://www.merg.info</a>.
028 *
029 * @author Kevin Dickerson Copyright (C) 2009
030 */
031public class MergSD2SignalHead extends DefaultSignalHead {
032
033    public MergSD2SignalHead(String sys, String user, int aspect, NamedBeanHandle<Turnout> t1, NamedBeanHandle<Turnout> t2, NamedBeanHandle<Turnout> t3, boolean feather, boolean home) {
034        super(sys, user);
035        mAspects = aspect;
036        mInput1 = t1;
037        if (t2 != null) {
038            mInput2 = t2;
039        }
040        if (t3 != null) {
041            mInput3 = t3;
042        }
043        mFeather = feather;
044        mHome = home;
045        if (mHome) {
046            MergSD2SignalHead.this.setAppearance(RED);
047        } else {
048            MergSD2SignalHead.this.setAppearance(YELLOW);
049        }
050    }
051
052    public MergSD2SignalHead(String sys, int aspect, NamedBeanHandle<Turnout> t1, NamedBeanHandle<Turnout> t2, NamedBeanHandle<Turnout> t3, boolean feather, boolean home) {
053        super(sys);
054        mAspects = aspect;
055        mInput1 = t1;
056        if (t2 != null) {
057            mInput2 = t2;
058        }
059        if (t3 != null) {
060            mInput3 = t3;
061        }
062        mFeather = feather;
063        mHome = home;
064        if (mHome) {
065            MergSD2SignalHead.this.setAppearance(RED);
066        } else {
067            MergSD2SignalHead.this.setAppearance(YELLOW);
068        }
069    }
070
071    /**
072     * Set the Signal Head Appearance.
073     * Modified from DefaultSignalHead. Removed option for software flashing.
074     *
075     * @param newAppearance integer representing a valid Appearance for this head
076     */
077    @Override
078    public void setAppearance(int newAppearance) {
079        int oldAppearance = mAppearance;
080        mAppearance = newAppearance;
081        boolean valid = false;
082        switch (mAspects) {
083            case 2:
084                if (mHome) {
085                    if ((newAppearance == RED) || (newAppearance == GREEN)) {
086                        valid = true;
087                    }
088                } else {
089                    if ((newAppearance == GREEN) || (newAppearance == YELLOW)) {
090                        valid = true;
091                    }
092                }
093                break;
094            case 3:
095                if ((newAppearance == RED) || (newAppearance == YELLOW) || (newAppearance == GREEN)) {
096                    valid = true;
097                }
098                break;
099            case 4:
100                if ((newAppearance == RED) || (newAppearance == YELLOW) || (newAppearance == GREEN) || (newAppearance == LUNAR)) {
101                    valid = true;
102                }
103                break;
104            default:
105                valid = false;
106                break;
107        }
108        if ((oldAppearance != newAppearance) && (valid)) {
109            updateOutput();
110
111            // notify listeners, if any
112            firePropertyChange("Appearance", oldAppearance, newAppearance);
113        }
114
115    }
116
117    @Override
118    public void setLit(boolean newLit) {
119        boolean oldLit = mLit;
120        mLit = newLit;
121        if (oldLit != newLit) {
122            updateOutput();
123            // notify listeners, if any
124            firePropertyChange("Lit", oldLit, newLit);
125        }
126    }
127
128    @Override
129    protected void updateOutput() {
130        // assumes that writing a turnout to an existing state is cheap!
131        switch (mAppearance) {
132            case RED:
133                mInput1.getBean().setCommandedState(Turnout.CLOSED);
134                //if(mInput2!=null) mInput2.setCommandedState(Turnout.CLOSED);
135                //if(mInput3!=null) mInput3.setCommandedState(Turnout.CLOSED);
136                break;
137            case YELLOW:
138                if (mHome) {
139                    mInput1.getBean().setCommandedState(Turnout.THROWN);
140                    if (mInput2 != null) {
141                        mInput2.getBean().setCommandedState(Turnout.CLOSED);
142                    }
143                } else {
144                    mInput1.getBean().setCommandedState(Turnout.CLOSED);
145                }
146                break;
147            case LUNAR:
148                mInput1.getBean().setCommandedState(Turnout.THROWN);
149                mInput2.getBean().setCommandedState(Turnout.THROWN);
150                mInput3.getBean().setCommandedState(Turnout.CLOSED);
151                //mInput1.setCommandedState(
152                //mFlashYellow.setCommandedState(mFlashYellowState);
153                break;
154            case GREEN:
155                mInput1.getBean().setCommandedState(Turnout.THROWN);
156                if (mInput2 != null) {
157                    mInput2.getBean().setCommandedState(Turnout.THROWN);
158                }
159                if (mInput3 != null) {
160                    mInput3.getBean().setCommandedState(Turnout.THROWN);
161                }
162                break;
163            default:
164                mInput1.getBean().setCommandedState(Turnout.CLOSED);
165
166                log.warn("Unexpected new appearance: {}", mAppearance);
167            // go dark
168            }
169        //}
170    }
171
172    /**
173     * Remove references to and from this object, so that it can eventually be
174     * garbage-collected.
175     */
176    @Override
177    public void dispose() {
178        mInput1 = null;
179        mInput2 = null;
180        mInput3 = null;
181        super.dispose();
182    }
183
184    NamedBeanHandle<Turnout> mInput1 = null; //Section directly infront of the Signal
185    NamedBeanHandle<Turnout> mInput2 = null; //Section infront of the next Signal
186    NamedBeanHandle<Turnout> mInput3 = null; //Section infront of the second Signal
187
188    int mAspects = 2;
189    boolean mFeather = false;
190    boolean mHome = true; //Home Signal = true, Distance Signal = false
191
192    @CheckForNull
193    public NamedBeanHandle<Turnout> getInput1() {
194        return mInput1;
195    }
196
197    @CheckForNull
198    public NamedBeanHandle<Turnout> getInput2() {
199        return mInput2;
200    }
201
202    @CheckForNull
203    public NamedBeanHandle<Turnout> getInput3() {
204        return mInput3;
205    }
206
207    /**
208     * Return the number of aspects for this signal.
209     *
210     * @return the number of aspects
211     */
212    public int getAspects() {
213        return mAspects;
214    }
215
216    public boolean getFeather() {
217        return mFeather;
218    }
219
220    /**
221     * Return whether this signal is a home or a distant/Repeater signal.
222     *
223     * @return true if signal is set up as Home signal (default); false if Distant
224     */
225    public boolean getHome() {
226        return mHome;
227    }
228
229    /**
230     * Set the first turnout used on the driver. Relates to the section directly
231     * in front of the Signal {@literal (2, 3 & 4 aspect Signals)}.
232     *
233     * @param t turnout (named bean handel) to use as input 1
234     */
235    public void setInput1(NamedBeanHandle<Turnout> t) {
236        mInput1 = t;
237    }
238
239    /**
240     * Set the second turnout used on the driver. Relates to the section in
241     * front of the next Signal (3 and 4 aspect Signal).
242     *
243     * @param t turnout (named bean handel) to use as input 2
244     */
245    public void setInput2(NamedBeanHandle<Turnout> t) {
246        mInput2 = t;
247    }
248
249    /**
250     * Set the third turnout used on the driver. Relates to the section directly
251     * in front the third Signal (4 aspect Signal).
252     *
253     * @param t turnout (named bean handel) to use as input 3
254     */
255    public void setInput3(NamedBeanHandle<Turnout> t) {
256        mInput3 = t;
257    }
258
259    /**
260     * Set the number of aspects on the signal.
261     *
262     * @param i the number of aspects on mast; valid values: 2, 3, 4
263     */
264    public void setAspects(int i) {
265        mAspects = i;
266    }
267
268    public void setFeather(boolean boo) {
269        mFeather = boo;
270    }
271
272    /**
273     * Set whether the signal is a home or distance/repeater signal.
274     *
275     * @param boo true if configuring as a Home signal, false for a Distant
276     */
277    public void setHome(boolean boo) {
278        mHome = boo;
279    }
280
281    final static private int[] validStates2AspectHome = new int[]{
282        RED,
283        GREEN
284    };
285    final static private String[] validStateKeys2AspectHome = new String[]{
286        "SignalHeadStateRed",
287        "SignalHeadStateGreen"
288    };
289
290    final static private int[] validStates2AspectDistant = new int[]{
291        YELLOW,
292        GREEN
293    };
294    final static private String[] validStateKeys2AspectDistant = new String[]{
295        "SignalHeadStateYellow",
296        "SignalHeadStateGreen"
297    };
298
299    final static private int[] validStates3Aspect = new int[]{
300        RED,
301        YELLOW,
302        GREEN
303    };
304    final static private String[] validStateKeys3Aspect = new String[]{
305        "SignalHeadStateRed",
306        "SignalHeadStateYellow",
307        "SignalHeadStateGreen"
308    };
309
310    final static private int[] validStates4Aspect = new int[]{
311        RED,
312        YELLOW,
313        LUNAR,
314        GREEN
315    };
316    final static private String[] validStateKeys4Aspect = new String[]{
317        "SignalHeadStateRed",
318        "SignalHeadStateYellow",
319        "SignalHeadStateLunar",
320        "SignalHeadStateGreen"
321    };
322
323    /**
324     * {@inheritDoc}
325     */
326    @Override
327    public int[] getValidStates() {
328        if (!mHome) {
329            return Arrays.copyOf(validStates2AspectDistant, validStates2AspectDistant.length);
330        } else {
331            switch (mAspects) {
332                case 2:
333                    return Arrays.copyOf(validStates2AspectHome, validStates2AspectHome.length);
334                case 3:
335                    return Arrays.copyOf(validStates3Aspect, validStates3Aspect.length);
336                case 4:
337                    return Arrays.copyOf(validStates4Aspect, validStates4Aspect.length);
338                default:
339                    log.warn("Unexpected number of aspects: {}", mAspects);
340                    return Arrays.copyOf(validStates3Aspect, validStates3Aspect.length);
341            }
342        }
343    }
344
345    /**
346     * {@inheritDoc}
347     */
348    @Override
349    public String[] getValidStateKeys() {
350        if (!mHome) {
351            return Arrays.copyOf(validStateKeys2AspectDistant, validStateKeys2AspectDistant.length);
352        } else {
353            switch (mAspects) {
354                case 2:
355                    return Arrays.copyOf(validStateKeys2AspectHome, validStateKeys2AspectHome.length);
356                case 3:
357                    return Arrays.copyOf(validStateKeys3Aspect, validStateKeys3Aspect.length);
358                case 4:
359                    return Arrays.copyOf(validStateKeys4Aspect, validStateKeys3Aspect.length);
360                default:
361                    log.warn("Unexpected number of aspects: {}", mAspects);
362                    return Arrays.copyOf(validStateKeys3Aspect, validStateKeys3Aspect.length);
363            }
364        }
365    }
366
367    /**
368     * {@inheritDoc}
369     */
370    @Override
371    public String[] getValidStateNames() {
372        String[] stateNames = new String[getValidStateKeys().length];
373        int i = 0;
374        for (String stateKey : getValidStateKeys()) {
375            stateNames[i++] = Bundle.getMessage(stateKey);
376        }
377        return stateNames;
378    }
379
380    @Override
381    public boolean isTurnoutUsed(Turnout t) {
382        if (mInput1 != null && t.equals(mInput1.getBean())) {
383            return true;
384        }
385        if (mInput2 != null && t.equals(mInput2.getBean())) {
386            return true;
387        }
388        return (mInput3 != null && t.equals(mInput3.getBean()));
389    }
390
391    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MergSD2SignalHead.class);
392
393}