<commit_before>/**
 * TeamCommViewer.java
 *
 * Created on 08.11.2010, 21:41:28
 */
package de.naoth.rc.dialogs;

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.stream.JsonWriter;
import de.naoth.rc.Helper;
import de.naoth.rc.RobotControl;
import de.naoth.rc.components.RobotStatus;
import de.naoth.rc.core.dialog.AbstractDialog;
import de.naoth.rc.core.dialog.DialogPlugin;
import de.naoth.rc.dataformats.SPLMessage;
import de.naoth.rc.drawingmanager.DrawingEventManager;
import de.naoth.rc.drawings.DrawingCollection;
import java.awt.Color;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.DatagramChannel;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import net.xeoh.plugins.base.annotations.PluginImplementation;
import net.xeoh.plugins.base.annotations.injections.InjectPlugin;

/**
 *
 * @author <NAME>ellmann
 */
public class TeamCommViewer extends AbstractDialog {

    @PluginImplementation
    public static class Plugin extends DialogPlugin<TeamCommViewer> {

        @InjectPlugin
        public static RobotControl parent;
        @InjectPlugin
        public static DrawingEventManager drawingEventManager;
    }//end Plugin

    private Timer timerCheckMessages;
    private final TeamCommListener listenerOwn = new TeamCommListener(false);
    private final TeamCommListener listenerOpponent = new TeamCommListener(true);
    private final HashMap<String, RobotStatus> robotsMap = new HashMap<>();

    private final Map<String, TeamCommMessage> messageMap = Collections.synchronizedMap(new TreeMap<String, TeamCommMessage>());

    private final Color magenta = new Color(210, 180, 200);
    private final Color cyan = new Color(180, 210, 255);
    
    private LogFileWriter logfile = null;
    private final ConcurrentLinkedQueue<TeamCommMessage> logfileQueue = new ConcurrentLinkedQueue<TeamCommMessage>();
    private boolean logfileQueueAppend = false;
    
    /**
     * Creates new form TeamCommViewer
     */
    public TeamCommViewer() {
        initComponents();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        teamCommFileChooser = new de.naoth.rc.components.ExtendedFileChooser();
        robotStatusPanel = new javax.swing.JPanel();
        btListen = new javax.swing.JToggleButton();
        portNumberOwn = new javax.swing.JFormattedTextField();
        portNumberOpponent = new javax.swing.JFormattedTextField();
        jLabel1 = new javax.swing.JLabel();
        jLabel2 = new javax.swing.JLabel();
        btnRecord = new javax.swing.JToggleButton();
        btnStopRecording = new javax.swing.JButton();

        teamCommFileChooser.setDialogType(javax.swing.JFileChooser.SAVE_DIALOG);
        teamCommFileChooser.setDialogTitle("Log file location");
        teamCommFileChooser.setSelectedFile(new File((new SimpleDateFormat("yyyy-MM-dd")).format(new Date())+"_TeamComm.log"));
        teamCommFileChooser.setToolTipText("");

        robotStatusPanel.setLayout(new java.awt.GridLayout(5, 1, 0, 5));

        btListen.setText("Listen to TeamComm");
        btListen.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btListenActionPerformed(evt);
            }
        });

        portNumberOwn.setColumns(6);
        portNumberOwn.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new javax.swing.text.NumberFormatter(new java.text.DecimalFormat("#"))));
        portNumberOwn.setText("10004");
        portNumberOwn.setToolTipText("Own team port number");

        portNumberOpponent.setColumns(6);
        portNumberOpponent.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new javax.swing.text.NumberFormatter(new java.text.DecimalFormat("#"))));
        portNumberOpponent.setToolTipText("Opponent team port number");

        jLabel1.setText("Blue:");

        jLabel2.setText("Red:");

        btnRecord.setIcon(new javax.swing.ImageIcon(getClass().getResource("/de/naoth/rc/res/play.png"))); // NOI18N
        btnRecord.setText("Record TeamComm");
        btnRecord.setActionCommand("RecordTeamComm");
        btnRecord.setSelectedIcon(new javax.swing.ImageIcon(getClass().getResource("/de/naoth/rc/res/media-record.png"))); // NOI18N
        btnRecord.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnRecordActionPerformed(evt);
            }
        });

        btnStopRecording.setIcon(new javax.swing.ImageIcon(getClass().getResource("/toolbarButtonGraphics/media/Stop24.gif"))); // NOI18N
        btnStopRecording.setToolTipText("Stops recording and closes log file.");
        btnStopRecording.setEnabled(false);
        btnStopRecording.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnStopRecordingActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(btListen)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(jLabel1)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(portNumberOwn, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jLabel2)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(portNumberOpponent, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addGap(12, 12, 12)
                        .addComponent(btnRecord)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnStopRecording)
                        .addGap(0, 100, Short.MAX_VALUE))
                    .addComponent(robotStatusPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(20, 20, 20)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
                    .addComponent(btnStopRecording, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)
                    .addComponent(btnRecord, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)
                    .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                        .addComponent(btListen)
                        .addComponent(portNumberOwn, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addComponent(portNumberOpponent, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addComponent(jLabel1)
                        .addComponent(jLabel2)))
                .addGap(27, 27, 27)
                .addComponent(robotStatusPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
    }// </editor-fold>//GEN-END:initComponents

    private void btListenActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btListenActionPerformed

        try {
            if (this.btListen.isSelected()) {
                String ownPortRaw = portNumberOwn.getText().trim();
                if(!ownPortRaw.isEmpty()) {
                    int portOwn = Integer.parseInt(ownPortRaw);
                    listenerOwn.connect(portOwn);
                }
                String opponentPortRaw = portNumberOpponent.getText().trim();
                if (!opponentPortRaw.isEmpty()) {
                    int portOpponent = Integer.parseInt(opponentPortRaw);
                    listenerOpponent.connect(portOpponent);
                }

                this.timerCheckMessages = new Timer();
                this.timerCheckMessages.scheduleAtFixedRate(new TeamCommListenTask(), 100, 33);
                this.portNumberOwn.setEnabled(false);
                this.portNumberOpponent.setEnabled(false);
                this.robotStatusPanel.setVisible(true);

            } else {

                if(this.timerCheckMessages != null) {
                    this.timerCheckMessages.cancel();
                    this.timerCheckMessages.purge();
                    this.timerCheckMessages = null;
                }
                
                listenerOwn.disconnect();
                listenerOpponent.disconnect();

                synchronized (messageMap) {
                    messageMap.clear();
                    this.robotsMap.clear();
                    this.robotStatusPanel.removeAll();
                    this.robotStatusPanel.setVisible(false);
                    this.portNumberOwn.setEnabled(true);
                    this.portNumberOpponent.setEnabled(true);
                }
            }
        } catch (NumberFormatException ex) {
            Helper.handleException("Invalid port number", ex);
        } catch (InterruptedException ex) {
            ex.printStackTrace(System.err);
        } catch (IOException ex) {
            ex.printStackTrace(System.err);
        }
    }//GEN-LAST:event_btListenActionPerformed

    private void btnRecordActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnRecordActionPerformed
        // button got pressed
        if(btnRecord.isSelected()) {
            // log file already set; just enable logging
            if(this.logfile != null) {
                logfileQueueAppend = true;
                btnRecord.setSelected(true);
                setBtnRecordToolTipText(true);
            } else {
                // select log file and enable logging!
                this.setLogFile();
            }
        // release button
        } else {
            if(logfile != null) { // be sure log file is set
                logfileQueueAppend = false;
                setBtnRecordToolTipText(false);
            }
        }
    }//GEN-LAST:event_btnRecordActionPerformed

    private void btnStopRecordingActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnStopRecordingActionPerformed
        // stop log file recording, flush and close logfile, update UI
        logfileQueueAppend = false;
        closingLogfile();
        btnRecord.setSelected(false);
        setBtnRecordToolTipText(false);
        btnStopRecording.setEnabled(false);
    }//GEN-LAST:event_btnStopRecordingActionPerformed

    @Override
    public void dispose() {
        closingLogfile();
    }

    @Override
    public void destroy() {
        System.err.println("destroy");
        super.destroy(); //To change body of generated methods, choose Tools | Templates.
    }
    
    private boolean setLogFile() {
        // open filechooser
        if(teamCommFileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
            // if selected file has a file extension - use this, otherwise append ".log" to the filename
            File dfile = (teamCommFileChooser.getSelectedFile().getName().lastIndexOf(".") == -1) ? 
                new File(teamCommFileChooser.getSelectedFile()+".log") : 
                teamCommFileChooser.getSelectedFile();

            if(dfile.canWrite()) {
                try {
                    // make sure there is no open log file ..
                    closingLogfile();

                    // create new log file<commit_msg>Bug fix: throws error msg (not writeable), if file doesn't exist ...<commit_after>/**
 * TeamCommViewer.java
 *
 * Created on 08.11.2010, 21:41:28
 */
package de.naoth.rc.dialogs;

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.stream.JsonWriter;
import de.naoth.rc.Helper;
import de.naoth.rc.RobotControl;
import de.naoth.rc.components.RobotStatus;
import de.naoth.rc.core.dialog.AbstractDialog;
import de.naoth.rc.core.dialog.DialogPlugin;
import de.naoth.rc.dataformats.SPLMessage;
import de.naoth.rc.drawingmanager.DrawingEventManager;
import de.naoth.rc.drawings.DrawingCollection;
import java.awt.Color;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.DatagramChannel;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import net.xeoh.plugins.base.annotations.PluginImplementation;
import net.xeoh.plugins.base.annotations.injections.InjectPlugin;

/**
 *
 * @author <NAME>
 */
public class TeamCommViewer extends AbstractDialog {

    @PluginImplementation
    public static class Plugin extends DialogPlugin<TeamCommViewer> {

        @InjectPlugin
        public static RobotControl parent;
        @InjectPlugin
        public static DrawingEventManager drawingEventManager;
    }//end Plugin

    private Timer timerCheckMessages;
    private final TeamCommListener listenerOwn = new TeamCommListener(false);
    private final TeamCommListener listenerOpponent = new TeamCommListener(true);
    private final HashMap<String, RobotStatus> robotsMap = new HashMap<>();

    private final Map<String, TeamCommMessage> messageMap = Collections.synchronizedMap(new TreeMap<String, TeamCommMessage>());

    private final Color magenta = new Color(210, 180, 200);
    private final Color cyan = new Color(180, 210, 255);
    
    private LogFileWriter logfile = null;
    private final ConcurrentLinkedQueue<TeamCommMessage> logfileQueue = new ConcurrentLinkedQueue<TeamCommMessage>();
    private boolean logfileQueueAppend = false;
    
    /**
     * Creates new form TeamCommViewer
     */
    public TeamCommViewer() {
        initComponents();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        teamCommFileChooser = new de.naoth.rc.components.ExtendedFileChooser();
        robotStatusPanel = new javax.swing.JPanel();
        btListen = new javax.swing.JToggleButton();
        portNumberOwn = new javax.swing.JFormattedTextField();
        portNumberOpponent = new javax.swing.JFormattedTextField();
        jLabel1 = new javax.swing.JLabel();
        jLabel2 = new javax.swing.JLabel();
        btnRecord = new javax.swing.JToggleButton();
        btnStopRecording = new javax.swing.JButton();

        teamCommFileChooser.setDialogType(javax.swing.JFileChooser.SAVE_DIALOG);
        teamCommFileChooser.setDialogTitle("Log file location");
        teamCommFileChooser.setSelectedFile(new File((new SimpleDateFormat("yyyy-MM-dd")).format(new Date())+"_TeamComm.log"));
        teamCommFileChooser.setToolTipText("");

        robotStatusPanel.setLayout(new java.awt.GridLayout(5, 1, 0, 5));

        btListen.setText("Listen to TeamComm");
        btListen.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btListenActionPerformed(evt);
            }
        });

        portNumberOwn.setColumns(6);
        portNumberOwn.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new javax.swing.text.NumberFormatter(new java.text.DecimalFormat("#"))));
        portNumberOwn.setText("10004");
        portNumberOwn.setToolTipText("Own team port number");

        portNumberOpponent.setColumns(6);
        portNumberOpponent.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new javax.swing.text.NumberFormatter(new java.text.DecimalFormat("#"))));
        portNumberOpponent.setToolTipText("Opponent team port number");

        jLabel1.setText("Blue:");

        jLabel2.setText("Red:");

        btnRecord.setIcon(new javax.swing.ImageIcon(getClass().getResource("/de/naoth/rc/res/play.png"))); // NOI18N
        btnRecord.setText("Record TeamComm");
        btnRecord.setActionCommand("RecordTeamComm");
        btnRecord.setSelectedIcon(new javax.swing.ImageIcon(getClass().getResource("/de/naoth/rc/res/media-record.png"))); // NOI18N
        btnRecord.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnRecordActionPerformed(evt);
            }
        });

        btnStopRecording.setIcon(new javax.swing.ImageIcon(getClass().getResource("/toolbarButtonGraphics/media/Stop24.gif"))); // NOI18N
        btnStopRecording.setToolTipText("Stops recording and closes log file.");
        btnStopRecording.setEnabled(false);
        btnStopRecording.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnStopRecordingActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(btListen)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(jLabel1)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(portNumberOwn, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jLabel2)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(portNumberOpponent, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addGap(12, 12, 12)
                        .addComponent(btnRecord)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnStopRecording)
                        .addGap(0, 100, Short.MAX_VALUE))
                    .addComponent(robotStatusPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(20, 20, 20)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
                    .addComponent(btnStopRecording, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)
                    .addComponent(btnRecord, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)
                    .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                        .addComponent(btListen)
                        .addComponent(portNumberOwn, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addComponent(portNumberOpponent, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addComponent(jLabel1)
                        .addComponent(jLabel2)))
                .addGap(27, 27, 27)
                .addComponent(robotStatusPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
    }// </editor-fold>//GEN-END:initComponents

    private void btListenActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btListenActionPerformed

        try {
            if (this.btListen.isSelected()) {
                String ownPortRaw = portNumberOwn.getText().trim();
                if(!ownPortRaw.isEmpty()) {
                    int portOwn = Integer.parseInt(ownPortRaw);
                    listenerOwn.connect(portOwn);
                }
                String opponentPortRaw = portNumberOpponent.getText().trim();
                if (!opponentPortRaw.isEmpty()) {
                    int portOpponent = Integer.parseInt(opponentPortRaw);
                    listenerOpponent.connect(portOpponent);
                }

                this.timerCheckMessages = new Timer();
                this.timerCheckMessages.scheduleAtFixedRate(new TeamCommListenTask(), 100, 33);
                this.portNumberOwn.setEnabled(false);
                this.portNumberOpponent.setEnabled(false);
                this.robotStatusPanel.setVisible(true);

            } else {

                if(this.timerCheckMessages != null) {
                    this.timerCheckMessages.cancel();
                    this.timerCheckMessages.purge();
                    this.timerCheckMessages = null;
                }
                
                listenerOwn.disconnect();
                listenerOpponent.disconnect();

                synchronized (messageMap) {
                    messageMap.clear();
                    this.robotsMap.clear();
                    this.robotStatusPanel.removeAll();
                    this.robotStatusPanel.setVisible(false);
                    this.portNumberOwn.setEnabled(true);
                    this.portNumberOpponent.setEnabled(true);
                }
            }
        } catch (NumberFormatException ex) {
            Helper.handleException("Invalid port number", ex);
        } catch (InterruptedException ex) {
            ex.printStackTrace(System.err);
        } catch (IOException ex) {
            ex.printStackTrace(System.err);
        }
    }//GEN-LAST:event_btListenActionPerformed

    private void btnRecordActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnRecordActionPerformed
        // button got pressed
        if(btnRecord.isSelected()) {
            // log file already set; just enable logging
            if(this.logfile != null) {
                logfileQueueAppend = true;
                btnRecord.setSelected(true);
                setBtnRecordToolTipText(true);
            } else {
                // select log file and enable logging!
                this.setLogFile();
            }
        // release button
        } else {
            if(logfile != null) { // be sure log file is set
                logfileQueueAppend = false;
                setBtnRecordToolTipText(false);
            }
        }
    }//GEN-LAST:event_btnRecordActionPerformed

    private void btnStopRecordingActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnStopRecordingActionPerformed
        // stop log file recording, flush and close logfile, update UI
        logfileQueueAppend = false;
        closingLogfile();
        btnRecord.setSelected(false);
        setBtnRecordToolTipText(false);
        btnStopRecording.setEnabled(false);
    }//GEN-LAST:event_btnStopRecordingActionPerformed

    @Override
    public void dispose() {
        closingLogfile();
    }

    @Override
    public void destroy() {
        System.err.println("destroy");
        super.destroy(); //To change body of generated methods, choose Tools | Templates.
    }
    
    private boolean setLogFile() {
        // open filechooser
        if(teamCommFileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
            // if selected file has a file extension - use this, otherwise append ".log" to the filename
            File dfile = (teamCommFileChooser.getSelectedFile().getName().lastIndexOf(".") == -1) ? 
                new File(teamCommFileChooser.getSelectedFile()+".log") : 
                teamCommFileChooser.getSelectedFile();
            // TODO: how to check if non-existing files are writeable?!
            if((dfile.exists() && dfile.canWrite()) || !dfile.exists()) {
                try {
                    // make sure there is no open log file ..
                    closingLogfile();

                    // create new log file
{'fraction_non_alphanumeric': 0.09934806229626947, 'fraction_numerical': 0.006446939514668598, 'mean_word_length': 2.5733143522712565, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2278299', 'n_tokens_mistral': 7608, 'n_tokens_neox': 7348, 'n_words': 1233}