001package jmri.jmrit.etcs.dmi.swing;
002
003import jmri.jmrit.etcs.CabMessage;
004import jmri.jmrit.etcs.ResourceUtil;
005
006import java.awt.*;
007import java.awt.event.*;
008import java.text.SimpleDateFormat;
009import java.util.*;
010import java.util.List;
011
012import javax.annotation.Nonnull;
013import javax.swing.*;
014
015/**
016 * Class for ERTMS DMI Panel E, the Driver Messages area.
017 * @author Steve Young Copyright (C) 2024
018 */
019public class DmiPanelE extends JPanel {
020
021    private final JButton e10upArrow;
022    private final JButton e11downArrow;
023
024    private final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm");
025
026    private final JLabel labele1;
027    private final DmiPanel mainPane;
028    private final JPanel messagePanel;
029    private final JButton messageButton;
030    private int msgScroll = 0;
031    private final List<DmiCabMessage> messageList = new ArrayList<>();
032    private final JLabel[] messageLabels;
033    private final JLabel[] timeLabels;
034
035    private final Font timeFont = new Font(DmiPanel.FONT_NAME, Font.PLAIN, 12);
036    private final Font messageFont = new Font(DmiPanel.FONT_NAME, Font.PLAIN, 16);
037
038    private CabMessage cabMessageBeingConfirmed;
039
040    private final JLabel msglabel1 = new JLabel();
041    private final JLabel msglabel2 = new JLabel();
042    private final JLabel msglabel3 = new JLabel();
043    private final JLabel msglabel4 = new JLabel();
044    private final JLabel msglabel5 = new JLabel();
045
046    private final JLabel timeLabel1 = new JLabel();
047    private final JLabel timeLabel2 = new JLabel();
048    private final JLabel timeLabel3 = new JLabel();
049    private final JLabel timeLabel4 = new JLabel();
050    private final JLabel timeLabel5 = new JLabel();
051
052    public DmiPanelE(@Nonnull DmiPanel mainPanel){
053        super();
054
055        mainPane = mainPanel;
056        messageButton = new JButton();
057
058        setLayout(null); // Set the layout manager to null
059
060        setBackground(DmiPanel.BACKGROUND_COLOUR);
061        setBounds(0, 365, 334, 100);
062
063        JPanel e1 = new JPanel();
064        JPanel e2 = new JPanel();
065        JPanel e3 = new JPanel();
066        JPanel e4 = new JPanel();
067        messagePanel = initMsgPanel();
068
069        e10upArrow = new JButton();
070        e11downArrow = new JButton();
071
072        e10upArrow.setFocusable(false);
073        e11downArrow.setFocusable(false);
074
075        e1.setBounds(0, 0, 54, 25);
076        e1.setBorder(BorderFactory.createLineBorder(Color.black, 1));
077        e1.setBackground(DmiPanel.BACKGROUND_COLOUR);
078
079        e2.setBounds(0, 25, 54, 25);
080        e2.setBorder(BorderFactory.createLineBorder(Color.black, 1));
081        e2.setBackground(DmiPanel.BACKGROUND_COLOUR);
082
083        e3.setBounds(0, 50, 54, 25);
084        e3.setBorder(BorderFactory.createLineBorder(Color.black, 1));
085        e3.setBackground(DmiPanel.BACKGROUND_COLOUR);
086
087        e4.setBounds(0, 75, 54, 25);
088        e4.setBorder(BorderFactory.createLineBorder(Color.black, 1));
089        e4.setBackground(DmiPanel.BACKGROUND_COLOUR);
090
091        messagePanel.setBounds(0, 0, 234, 100);
092        messagePanel.setBorder(BorderFactory.createLineBorder(Color.black, 1));
093        messagePanel.setBackground(DmiPanel.BACKGROUND_COLOUR);
094
095        messageButton.setBounds(54, 0, 234, 100);
096        messageButton.setLayout(null);
097        messageButton.setContentAreaFilled(false); // Make the button transparent
098        messageButton.setBorderPainted(false); // Remove button border
099        messageButton.setName("messageAcknowledgeButton");
100        messageButton.add(messagePanel);
101        messageButton.addActionListener(this::acknowledgeButtonPressed);
102
103        e10upArrow.setBounds(234+54, 0, 46, 50);
104        e10upArrow.setBorder(BorderFactory.createLineBorder(Color.black, 1));
105        e10upArrow.setBackground(DmiPanel.BACKGROUND_COLOUR);
106
107        e11downArrow.setBounds(234+54, 50, 46, 50);
108        e11downArrow.setBorder(BorderFactory.createLineBorder(Color.black, 1));
109        e11downArrow.setBackground(DmiPanel.BACKGROUND_COLOUR);
110
111        e10upArrow.setIcon(ResourceUtil.getImageIcon("NA_13.bmp"));
112        e11downArrow.setIcon(ResourceUtil.getImageIcon("NA_14.bmp"));
113        e10upArrow.setDisabledIcon(ResourceUtil.getImageIcon("NA_15.bmp"));
114        e11downArrow.setDisabledIcon(ResourceUtil.getImageIcon("NA_16.bmp"));
115        e10upArrow.setName("e10upArrow");
116        e11downArrow.setName("e11downArrow");
117        e10upArrow.addActionListener( (ActionEvent e) -> { msgScroll--; updateMsgPanel(); });
118        e11downArrow.addActionListener( (ActionEvent e) -> { msgScroll++; updateMsgPanel(); });
119
120        add(e1);
121        add(e2);
122        add(e3);
123        add(e4);
124        add(messageButton);
125
126        add(e10upArrow);
127        add(e11downArrow);
128
129        labele1 = new JLabel();
130        e1.add(labele1);
131
132        timeLabels = new JLabel[]{timeLabel1,timeLabel2,timeLabel3,timeLabel4,timeLabel5};
133        messageLabels = new JLabel[]{msglabel1,msglabel2,msglabel3,msglabel4,msglabel5};
134
135        e10upArrow.setEnabled(false);
136        e11downArrow.setEnabled(false);
137    }
138
139    private void acknowledgeButtonPressed(ActionEvent e){
140        setMessageButtonEnabled(false);
141        cabMessageBeingConfirmed.setConfirmed();
142        mainPane.firePropertyChange(DmiPanel.PROP_CHANGE_CABMESSAGE_ACK, cabMessageBeingConfirmed.getMessageId());
143        updateMsgPanel();
144    }
145
146    private JPanel initMsgPanel() {
147        JPanel p = new JPanel();
148        p.setLayout(null);
149
150        timeLabel1.setBounds(5, 0, 35, 20);
151        timeLabel2.setBounds(5, 20, 35, 20);
152        timeLabel3.setBounds(5, 40, 35, 20);
153        timeLabel4.setBounds(5, 60, 35, 20);
154        timeLabel5.setBounds(5,80, 35, 20);
155
156        msglabel1.setBounds(50,  0, 204, 20);
157        msglabel2.setBounds(50, 20, 204, 20);
158        msglabel3.setBounds(50, 40, 204, 20);
159        msglabel4.setBounds(50, 60, 204, 20);
160        msglabel5.setBounds(50, 80, 204, 20);
161
162        timeLabel1.setBackground(DmiPanel.BACKGROUND_COLOUR);
163        timeLabel2.setBackground(DmiPanel.BACKGROUND_COLOUR);
164        timeLabel3.setBackground(DmiPanel.BACKGROUND_COLOUR);
165        timeLabel4.setBackground(DmiPanel.BACKGROUND_COLOUR);
166        timeLabel5.setBackground(DmiPanel.BACKGROUND_COLOUR);
167
168        msglabel1.setBackground(DmiPanel.BACKGROUND_COLOUR);
169        msglabel2.setBackground(DmiPanel.BACKGROUND_COLOUR);
170        msglabel3.setBackground(DmiPanel.BACKGROUND_COLOUR);
171        msglabel4.setBackground(DmiPanel.BACKGROUND_COLOUR);
172        msglabel5.setBackground(DmiPanel.BACKGROUND_COLOUR);
173
174        timeLabel1.setForeground(Color.WHITE);
175        timeLabel2.setForeground(Color.WHITE);
176        timeLabel3.setForeground(Color.WHITE);
177        timeLabel4.setForeground(Color.WHITE);
178        timeLabel5.setForeground(Color.WHITE);
179
180        msglabel1.setForeground(Color.WHITE);
181        msglabel2.setForeground(Color.WHITE);
182        msglabel3.setForeground(Color.WHITE);
183        msglabel4.setForeground(Color.WHITE);
184        msglabel5.setForeground(Color.WHITE);
185
186        timeLabel1.setFont(timeFont);
187        timeLabel2.setFont(timeFont);
188        timeLabel3.setFont(timeFont);
189        timeLabel4.setFont(timeFont);
190        timeLabel5.setFont(timeFont);
191
192        msglabel1.setFont(messageFont);
193        msglabel2.setFont(messageFont);
194        msglabel3.setFont(messageFont);
195        msglabel4.setFont(messageFont);
196        msglabel5.setFont(messageFont);
197
198        msglabel1.setName("msglabel1");
199        msglabel5.setName("msglabel5");
200        timeLabel1.setName("timeLabel1");
201
202        p.add(timeLabel1);
203        p.add(timeLabel2);
204        p.add(timeLabel3);
205        p.add(timeLabel4);
206        p.add(timeLabel5);
207
208        p.add(msglabel1);
209        p.add(msglabel2);
210        p.add(msglabel3);
211        p.add(msglabel4);
212        p.add(msglabel5);
213
214        return p;
215    }
216
217    protected void addMessage(@Nonnull CabMessage msg){
218        // replace existing message with same ID
219        removeMessage(msg.getMessageId());
220        messageList.add(new DmiCabMessage(msg, messageFont));
221        msgScroll = 0;
222        updateMsgPanel();
223    }
224
225    protected void removeMessage(String messageId){
226        Iterator<DmiCabMessage> iterator = messageList.iterator();
227        while (iterator.hasNext()) {
228            DmiCabMessage obj = iterator.next();
229            if (obj.getMessageId().equals(messageId)) {
230                iterator.remove(); // Remove the object from the list
231            }
232        }
233        updateMsgPanel();
234    }
235
236    private void updateMsgPanel(){
237        // log.info("starting update");
238        Comparator<CabMessage> customComparator = Comparator
239            .comparing(CabMessage::getAckRequired, Comparator.reverseOrder()) // Sort by boolean value (true first)
240            .thenComparingInt(CabMessage::getGroup) // Then sort by integer value (low to high)
241            .thenComparing(CabMessage::getSentTime, Comparator.reverseOrder()); // Then sort by time (newest first)
242
243        // Sort the list using the custom comparator
244        Collections.sort(messageList, customComparator);
245
246        // reset previous message display
247        for (int i = 0; i < 5; i++){
248            messageLabels[i].setText("");
249            timeLabels[i].setText("");
250        }
251
252        if ( !messageList.isEmpty()) {
253            DmiCabMessage msg = messageList.get(0); 
254            if ( msg.getAckRequired() ) {
255                setAckReqdMessage(msg);
256                return;
257            }
258        }
259
260        displayMessages();
261    }
262
263    private void displayMessages() {
264        setMessageButtonEnabled(false);
265
266        List<String> tempTimes = new ArrayList<>();
267        List<String> tempMessages = new ArrayList<>();
268        for ( DmiCabMessage msg : messageList) {
269            log.debug("CabM: {}", msg);
270            String[] msgText = msg.getMessageArray();
271            for (int i = 0; i < msgText.length; i++){
272                tempTimes.add( i==0 ? dateFormat.format(msg.getSentTime().getTime()): "");
273                tempMessages.add(msgText[i]);
274            }
275        }
276
277        msgScroll = Math.min(msgScroll, Math.max(tempMessages.size()-5, 0));
278        for (int i = 0; i < 5; i++){
279            int position = i + msgScroll;
280            if ( position < tempMessages.size() ) {
281                timeLabels[i].setText(tempTimes.get(position));
282                messageLabels[i].setText(tempMessages.get(position));
283            }
284        }
285        e10upArrow.setEnabled(msgScroll > 0);
286        e11downArrow.setEnabled(msgScroll < tempMessages.size()-5);
287    }
288
289    private void setAckReqdMessage(DmiCabMessage msg){
290        log.debug("ack reqd, display single message");
291        setMessageButtonEnabled(true);
292
293        timeLabels[0].setText(dateFormat.format(msg.getSentTime().getTime()));
294        cabMessageBeingConfirmed = msg;
295
296        String[] msgText = msg.getMessageArray();
297        log.debug("formatted msg has {} lines", msgText.length);
298
299        for (int i = 0; i < 5; i++){
300            if ( i < msgText.length ){
301                log.debug("msgText {} {}", i, msgText[i]);
302                messageLabels[i].setText(msgText[i]);
303            }
304        }
305        e10upArrow.setEnabled(false);
306        e11downArrow.setEnabled(false);
307    }
308
309    private void setMessageButtonEnabled(boolean newVal) {
310        messageButton.setEnabled(newVal);
311        messagePanel.setBorder(BorderFactory.createLineBorder(
312            newVal ? DmiPanel.YELLOW : DmiPanel.BACKGROUND_COLOUR, 2));
313    }
314
315    // 1 ok, 0 conn  lost, -1 not visible
316    protected void setSafeRadioConnection(int newVal) {
317        switch (newVal) {
318            case 1:
319                labele1.setIcon(ResourceUtil.getImageIcon("ST_03.bmp"));
320                labele1.setToolTipText(Bundle.getMessage("RadioConnectionOK"));
321                break;
322            case 0:
323                labele1.setIcon(ResourceUtil.getImageIcon("ST_04.bmp"));
324                labele1.setToolTipText(Bundle.getMessage("RadioConnectionLost"));
325                break;
326            default:
327                labele1.setIcon(null);
328                labele1.setToolTipText(null);
329                break;
330        }
331    }
332
333    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DmiPanelE.class);
334
335}