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