/* 
 * Copyright (C) 2017 John Garner
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.pikatimer.timing.reader;

import com.pikatimer.PikaPreferences;
import com.pikatimer.event.Event;
import com.pikatimer.timing.RawTimeData;
import com.pikatimer.timing.TimingListener;
import com.pikatimer.timing.TimingReader;
import com.pikatimer.util.DurationFormatter;
import com.pikatimer.util.DurationParser;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.TimeZone;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.Spinner;
import javafx.scene.control.TextField;
import javafx.scene.control.TitledPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import static javafx.scene.layout.Region.USE_PREF_SIZE;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Screen;
import org.controlsfx.control.PrefixSelectionComboBox;
import org.controlsfx.control.ToggleSwitch;

/**
 *
 * @author jcgarner
 */


/**
 * 
 * This class implements the communications to and from the RFID Ultra/Joey
 * 
 * It is a minefield of threads as the system has to mux/demux the requests 
 * and responses to and from the unit over a single TCP connection. The system
 * uses the "okToSend" semaphore to ensure that only one request is 
 * outstanding at any given time. 
 * 
 * Everything should be done in an async fashion to keep the UI responsive. 
 * Given that everything is contained in this one class, it was decided 
 * to not use a message bus library. 
 * 
 * Proceed with caution. 
 * 
 */



public class PikaRFIDDirectReader implements TimingReader {

    LocalDateTime EPOC = LocalDateTime.of(LocalDate.parse("1980-01-01",DateTimeFormatter.ISO_LOCAL_DATE),LocalTime.MIDNIGHT);
    
    protected TimingListener timingListener;
    protected String ultraIP;
    
    Thread ultraConnectionThread;
    InputStream input = null;
    DataOutputStream ultraOutput = null;
    private static final BlockingQueue<String> commandResultQueue = new ArrayBlockingQueue(10);

    private Pane displayPane; 
    private Button discoverButton;
    private Button rewindButton;
    protected TextField ultraIPTextField; 
    protected TextField readsFileTextField;
    protected Label statusLabel; 
    protected Label lastReadLabel;
    private HBox displayHBox; 
    private VBox displayVBox; 
    private ProgressBar batteryProgressBar;
    private ToggleSwitch connectToggleSwitch;
    private ToggleSwitch readToggleSwitch;
    
    CheckBox saveToFileCheckBox = new CheckBox("Save to File:");
    TextField fileTextField = new TextField();
    Button fileButton = new Button("Select...");
            
    ChoiceBox<String> reader1ModeChoiceBox = new ChoiceBox(FXCollections.observableArrayList("Start", "Finish"));
    Spinner<Integer> gatingIntervalSpinner = new Spinner<>(1, 20, 3);    
    ChoiceBox<String> beeperVolumeChoiceBox = new ChoiceBox(FXCollections.observableArrayList("Off", "Soft", "Loud"));
    
    Button setClockButton = new Button("Sync Time...");
    Button remoteSettingsButton = new Button("Remote Server...");
    Button antennaSettingsButton = new Button("Antenna Options...");
    Label modeLabel = new Label("Reader Mode:");
    Label gatingLabel = new Label("Gating Interval:");
    Label volumeLabel = new Label("Beeper Volume:");
    Button updateSettingsButton = new Button("Update");
            
    private Map<String,String> ultraSettings = new HashMap();
    private UltraClock ultraClock = new UltraClock();

    
    protected final BooleanProperty readingStatus = new SimpleBooleanProperty();
    protected final BooleanProperty connectedStatus = new SimpleBooleanProperty();
    protected final BooleanProperty isJoey = new SimpleBooleanProperty(false);
    protected final BooleanProperty isSingleReader = new SimpleBooleanProperty(true);

    private Boolean connectToUltra = false;
    private Boolean externalInitiated = false;
    
    protected final BooleanProperty clockIssues = new SimpleBooleanProperty(false);
    
    private int lastRead = -1;
    
    Semaphore okToSend = new Semaphore(1);
    
    private Boolean saveToFile = false;
    private String backupFile = null;
    private PrintWriter outputFile = null;

    
    public PikaRFIDDirectReader() {
    }

    @Override
    public void setTimingListener(TimingListener t) {
                timingListener = t; 
        
        // get any existing attributes
        ultraIP = timingListener.getAttribute("RFIDDirect:ultra_ip");
        if (ultraIP != null) {
            System.out.println("RFIDDirect: Found existing ultra ip setting: " + ultraIP);
        } else {
            System.out.println("RFIDDirect: Did not find existing ip setting." );
            ultraIP = "";
            timingListener.setAttribute("RFIDDirect:ultra_ip", ultraIP);
        }
        
        saveToFile = Boolean.valueOf(timingListener.getAttribute("RFIDDirect:saveToFile"));
        if (ultraIP != null) {
            System.out.println("RFIDDirect: Found existing saveToFile setting: " + saveToFile);
        } else {
            System.out.println("RFIDDirect: Did not find existing saveToFile setting." );
            saveToFile = false;
            timingListener.setAttribute("RFIDDirect:saveToFile", saveToFile.toString());
        }
        
        backupFile = timingListener.getAttribute("RFIDDirect:backupFile");
        if (backupFile != null) {
            System.out.println("RFIDDirect: Found existing backupFile setting: " + backupFile);
        } else {
            System.out.println("RFIDDirect: Did not find existing ip setting." );
            backupFile = "";
            timingListener.setAttribute("RFIDDirect:backupFile", backupFile);
        }
    }

    @Override
    public void showControls(Pane p) {
        if (displayPane == null) {
            // initialize our display
            displayHBox = new HBox();
            displayVBox = new VBox();
            batteryProgressBar = new ProgressBar();
            connectToggleSwitch = new ToggleSwitch("Connect");
            connectToggleSwitch.selectedProperty().set(false);

            connectToggleSwitch.maxWidth(30);
            HBox.setHgrow(connectToggleSwitch, Priority.NEVER);

            
            
            connectToggleSwitch.setPadding(new Insets(3, 0, 0, 0)); // this is a hack to get around a ToggleSwitch bug

            readToggleSwitch = new ToggleSwitch("Read");
            readToggleSwitch.selectedProperty().set(false);
            readToggleSwitch.disableProperty().bind(connectedStatus.not());
            readToggleSwitch.setPadding(new Insets(3, 0, 0, 0)); // this is a hack to get around a ToggleSwitch bug
            readToggleSwitch.maxWidth(30);
            HBox.setHgrow(readToggleSwitch, Priority.NEVER);
            
            HBox switchHBox = new HBox();
            switchHBox.maxHeight(18);
            switchHBox.prefWidth(30);
            switchHBox.maxWidth(30);
            switchHBox.setSpacing(5);
            switchHBox.getChildren().addAll(connectToggleSwitch,readToggleSwitch);
            
            Label ipLabel = new Label("Ultra IP:");
            statusLabel = new Label("Disconnected");
            statusLabel.setPrefWidth(200);
            lastReadLabel = new Label("");
            lastReadLabel.setPrefWidth(300);
            discoverButton = new Button("Discover...");
            rewindButton = new Button("Rewind...");
            ultraIPTextField = new TextField();
            ultraIPTextField.setPrefWidth(90);
            ultraIPTextField.setMinWidth(USE_PREF_SIZE);
            displayVBox.setSpacing(5); 
            //displayVBox.setPadding(new Insets(5, 5, 5, 5));
            
            connectToggleSwitch.selectedProperty().bindBidirectional(connectedStatus);
            readToggleSwitch.selectedProperty().bindBidirectional(readingStatus);
            
            ultraIPTextField.disableProperty().bind(connectToggleSwitch.selectedProperty()); // no changes when connected
            
            // This is way more complicated than it should be...
            ultraIPTextField.textProperty().addListener((observable, oldValue, newValue) -> {
                Boolean revert = false;
                if (newValue.isEmpty()) connectToggleSwitch.disableProperty().set(true);
                if (ultraIP == null || newValue.isEmpty() || ultraIP.equals(newValue)) {
                    connectToggleSwitch.disableProperty().set(false);
                    return;
                }
                if (newValue.matches("[\\d\\.]+")) { // numbers and dots only for the inital pass
                    String octets[] = newValue.split("\\.");
                    if (octets.length != 4) connectToggleSwitch.disableProperty().set(true);
                    if (octets.length > 4){ // too many octets, cut a few and it will be fine
                        revert = true;
                    } else {
                        Boolean validIP = true;
                        for(String octet: octets) {
                            try {
                                Integer o = Integer.parseInt(octet);
                                System.out.println("Octet : " + o);
                                if (o > 255) {
                                    validIP = false;
                                    revert = true;
                                }
                            } catch (Exception e){
                                System.out.println("Octet Exception: " + e.getLocalizedMessage());

                                validIP = false;
                            }
                        }
                        if (validIP && octets.length == 4) {
                            System.out.println("Valid IP : " + ultraIP);
                            connectToggleSwitch.disableProperty().set(false);
                            // save the ip if it is new
                            if (!ultraIP.equals(newValue)) {
                                ultraIP = newValue;
                                timingListener.setAttribute("RFIDDirect:ultra_ip", ultraIP);
                                System.out.println("Valid IP : " + ultraIP);
                            }
                        }
                        else{
                            connectToggleSwitch.disableProperty().set(true);
                        }
                    }
                } else { //just say no
                    revert = true;
                }
                if (revert) {
                        connectToggleSwitch.disableProperty().set(true);
                        Platform.runLater(() -> { 
                        int c = ultraIPTextField.getCaretPosition();
                        ultraIPTextField.setText(oldValue);
                        ultraIPTextField.positionCaret(c);
                    }); 
                }
            });
            TitledPane settingsTitlePane = new TitledPane();
            VBox settingsVBox = new VBox();
            settingsVBox.setSpacing(4);
            
            HBox fileHBox = new HBox();
            fileHBox.setSpacing(4);
            fileHBox.setAlignment(Pos.CENTER_LEFT);
            fileTextField.setPrefWidth(150);
            fileHBox.getChildren().addAll(saveToFileCheckBox,fileTextField,fileButton);
            
            VBox advancedVBox = new VBox();
            advancedVBox.setSpacing(4);
            advancedVBox.setAlignment(Pos.CENTER_LEFT);
            
            gatingIntervalSpinner.setMaxWidth(60);
            gatingIntervalSpinner.setEditable(true);
            
            // REMOVE THIS once JDK-8150946 is backported or we upgrade to JDK 9
            gatingIntervalSpinner.focusedProperty().addListener((observable, oldValue, newValue) -> {
                if (!newValue) {
                    gatingIntervalSpinner.increment(0); // won't change value, but will commit editor
                    Integer gating =Integer.parseInt(ultraSettings.get("1E"));
                    if (!gatingIntervalSpinner.getValue().equals(gating)) updateSettingsButton.setVisible(true);
                }
            });
            reader1ModeChoiceBox.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> {
                if ("Start".equals(newVal)) {
                    gatingIntervalSpinner.disableProperty().set(true);
                    if (!"0".equals(ultraSettings.get("14"))) updateSettingsButton.setVisible(true);
                }
                else {
                    gatingIntervalSpinner.disableProperty().set(false);
                    if (!"3".equals(ultraSettings.get("14"))) {
                        updateSettingsButton.setVisible(true);
                        gatingIntervalSpinner.getValueFactory().setValue(5);
                    }

                }
            });
            
            modeLabel.managedProperty().bind(isJoey.not());
            modeLabel.visibleProperty().bind(isJoey.not());
            reader1ModeChoiceBox.managedProperty().bind(isJoey.not());
            reader1ModeChoiceBox.visibleProperty().bind(isJoey.not());
            
            beeperVolumeChoiceBox.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> {
                System.out.println("beeperVolumeChoiceBox listener: Existing volume: " + ultraSettings.get("21"));
                if (null != newVal) switch (newVal) {
                    case "Off":
                        if (!"0".equals(ultraSettings.get("21"))) updateSettingsButton.setVisible(true);
                        break;
                    case "Soft":
                        if (!"1".equals(ultraSettings.get("21"))) updateSettingsButton.setVisible(true);
                        break;
                    case "Loud":
                        if (!"2".equals(ultraSettings.get("21"))) updateSettingsButton.setVisible(true);
                        break;
                    default:
                        break;
                }
                
            });
            
            updateSettingsButton.setVisible(false);
            updateSettingsButton.setOnAction(action -> {
                updateReaderSettings();
            });
            
            HBox advancedHBox1 = new HBox();
            advancedHBox1.setSpacing(4);
            advancedHBox1.setAlignment(Pos.CENTER_LEFT);
            HBox advancedHBox2 = new HBox();
            advancedHBox2.setSpacing(4);
            advancedHBox2.setAlignment(Pos.CENTER_LEFT);
            HBox advancedHBox3 = new HBox();
            advancedHBox3.setSpacing(4);
            advancedHBox3.setAlignment(Pos.CENTER_LEFT);
            
            advancedHBox1.getChildren().addAll(volumeLabel,beeperVolumeChoiceBox,modeLabel,reader1ModeChoiceBox,gatingLabel,gatingIntervalSpinner);
            advancedHBox2.getChildren().addAll(setClockButton,remoteSettingsButton,antennaSettingsButton);
            advancedHBox3.getChildren().addAll(updateSettingsButton);
            advancedVBox.getChildren().addAll(advancedHBox1,advancedHBox2,advancedHBox3);
                        
            advancedVBox.disableProperty().bind(connectedStatus.and(readingStatus).or(connectedStatus.not()));
            
            settingsVBox.getChildren().addAll(fileHBox,advancedVBox);
            
            settingsTitlePane.setText("Advanced Settings");
            settingsTitlePane.setContent(settingsVBox);
            settingsTitlePane.setExpanded(false);
            HBox statusHBox = new HBox();
            statusHBox.getChildren().addAll(statusLabel,lastReadLabel);
            statusHBox.setSpacing(5);
            
            Label strut = new Label();
            strut.setMaxWidth(Double.MAX_VALUE);
            HBox.setHgrow(strut, Priority.ALWAYS);
            displayHBox.setSpacing(5);
            displayHBox.setAlignment(Pos.CENTER_LEFT);
            displayHBox.getChildren().addAll(ipLabel,ultraIPTextField, discoverButton, switchHBox,strut, rewindButton); 
            displayVBox.setAlignment(Pos.CENTER_LEFT);
            displayVBox.getChildren().addAll(displayHBox,statusHBox ,settingsTitlePane); 
            
            // Set the action for the discoverButton
            discoverButton.setOnAction((event) -> {
                discover();
            });
            discoverButton.disableProperty().bind(connectedStatus);
            
            rewindButton.setOnAction((event) -> {
                rewind();
            });
            rewindButton.disableProperty().bind(connectedStatus.not());
            
            setClockButton.setOnAction((event) -> {
                setClockDialog();
            });
            remoteSettingsButton.setOnAction((event) -> {
                remoteDialog();
            });
            antennaSettingsButton.setOnAction((event) -> {
                antennaDialog();
            });
            batteryProgressBar.visibleProperty().bind(connectToggleSwitch.selectedProperty());
            batteryProgressBar.setMaxHeight(30.0);
            
            connectToggleSwitch.selectedProperty().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
                if(newValue) {
                    if (!connectToUltra) {
                        System.out.println("PikaRFIDDirectReader: connectToggleSwitch event: calling connect()");
                        connect();
                    }
                } else {
                    if (connectToUltra) {
                        System.out.println("PikaRFIDDirectReader: connectToggleSwitch event: calling disconnect()");
                        disconnect();
                    }
                }
            });
            
            readToggleSwitch.selectedProperty().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
                if(newValue) {
                    if (connectToUltra && !externalInitiated) {
                        System.out.println("PikaRFIDDirectReader: readToggleSwitch event: calling startReading()");
                        startReading();
                    }
                } else {
                    if (connectToUltra && !externalInitiated) {
                        System.out.println("PikaRFIDDirectReader: readToggleSwitch event: calling stopReading()");
                        stopReading();
                    }
                }
                externalInitiated = false;
            });
            ultraIPTextField.textProperty().setValue(ultraIP);
            
            // save to file stuff
            saveToFileCheckBox.setSelected(saveToFile);
            saveToFileCheckBox.selectedProperty().addListener((ob, oldVal, newVal) -> {
                System.out.println("saveToFileCheckBox changed: " + oldVal + " -> " + newVal);
                if (!newVal.equals(saveToFile)){
                    saveToFile=newVal;
                    timingListener.setAttribute("RFIDDirect:saveToFile", saveToFile.toString());
                }
                
            });
            
            fileTextField.setText(backupFile);
            fileTextField.focusedProperty().addListener((ob, oldVal, newVal) -> {
                if (!newVal) {
                    outputFile = null; // clear the reference to the old file
                    if (fileTextField.getText().isEmpty()){
                        saveToFileCheckBox.setSelected(false);
                        if (!backupFile.isEmpty()) {
                            
                            backupFile="";
                            timingListener.setAttribute("RFIDDirect:backupFile", backupFile);
                        }
                        return;
                    }
                    
                    File newFile = new File(fileTextField.getText()).getAbsoluteFile();
                    System.out.println("Testing file " + newFile.getAbsolutePath());
                    Boolean goodFile=false;
                    try {
                        if (newFile.canWrite() || newFile.createNewFile()) {
                            backupFile=newFile.getPath();
                            timingListener.setAttribute("RFIDDirect:backupFile", fileTextField.getText());
                            goodFile=true;
                        }
                    } catch (IOException ex) {
                        //Logger.getLogger(PikaRFIDDirectReader.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    if (!goodFile) {
                        // warn and revert
                        Platform.runLater(() -> {
                            Alert alert = new Alert(AlertType.ERROR);
                            alert.setTitle("Unable to write to file");
                            alert.setHeaderText("Unable to write to the selected file!");
                            alert.setContentText("The chosen file path, " +  newFile.getPath() + "\neither does not exist or is not writable.");
                            alert.showAndWait();
                        });
                        fileTextField.setText(backupFile);
                        saveToFileCheckBox.setSelected(false);
                    }
                }
            });
                    
                    
            fileButton.setOnAction(event -> {
                final FileChooser fileChooser = new FileChooser();
        
                fileChooser.setTitle("Save File");

                File cwd = PikaPreferences.getInstance().getCWD();


                System.out.println("Using initial directory of " + cwd.getAbsolutePath());
                
                fileChooser.setInitialFileName(timingListener.getLocationName() + ".txt");
                fileChooser.setInitialDirectory(cwd); 
                fileChooser.getExtensionFilters().addAll(
                        new FileChooser.ExtensionFilter("Text Files", "*.txt"),
                        new FileChooser.ExtensionFilter("All files", "*")
                    );
                File file = fileChooser.showSaveDialog(fileButton.getScene().getWindow());
                if (file != null) {
                    Platform.runLater(() -> fileTextField.setText(file.getAbsolutePath()));
                    outputFile=null;
                }
            });
            
        }
        
        // If we were previously visible, clear the old one
        if (displayPane != null) displayPane.getChildren().clear();
        
        // Now show ourselves.... 
        displayPane = p; 
        displayPane.getChildren().clear();
        displayPane.getChildren().add(displayVBox);
    }

    @Override
    public void readOnce() {
        // noop
    }

    @Override
    public void startReading() {
        Task ultraCommand = new Task<Void>() {
                @Override public Void call() {
                    if (connectedStatus.get()) {
                        Boolean aquired = false;
                        try {
                            if (okToSend.tryAcquire(10, TimeUnit.SECONDS)){
                                aquired=true;
                                System.out.println("Sending command 'R'");
                                Platform.runLater(() -> {
                                    statusLabel.setText("Starting Readers...");
                                });

                                ultraOutput.writeBytes("R");
                                ultraOutput.flush();
                                Thread.sleep(5000); // give the readers 5 seconds to start before we check
                                getReadStatus(); 
                            } else {
                                // timeout
                                System.out.println("Timeout with command 'R'");
                            }
                        } catch (Exception ex) {
                            Logger.getLogger(PikaRFIDDirectReader.class.getName()).log(Level.SEVERE, null, ex);

                        } finally {
                            if (aquired) okToSend.release();
                        }
                    }
                    return null;
                }
        };
        new Thread(ultraCommand).start();
    }

    @Override
    public void stopReading() {
        Task ultraCommand = new Task<Void>() {
                @Override public Void call() {
                    if (connectedStatus.get()) {
                        Boolean aquired = false;
                        try {
                            if (okToSend.tryAcquire(10, TimeUnit.SECONDS)){
                                aquired = true;
                                Platform.runLater(() -> {
                                    statusLabel.setText("Stopping Readers...");
                                });
                                System.out.println("Sending command 'S\\nN\\n'");
                                ultraOutput.writeBytes("S");
                                ultraOutput.flush();
                                ultraOutput.writeBytes("N");
                                ultraOutput.flush();
                                Thread.sleep(5000); // give the readers 5 seconds to stop before we check
                                getReadStatus();
                            } else {
                                // timeout
                                System.out.println("Timeout with command 'S'");
                            }
                        } catch (InterruptedException ex) {
                            Logger.getLogger(PikaRFIDDirectReader.class.getName()).log(Level.SEVERE, null, ex);

                        } catch (IOException ex) {
                            Logger.getLogger(PikaRFIDDirectReader.class.getName()).log(Level.SEVERE, null, ex);
                        } finally {
                            if (aquired) okToSend.release();
                        }
                    }
                    return null;
                }
        };
        new Thread(ultraCommand).start();
    }

    private void getReadStatus(){
        Task ultraCommand = new Task<Void>() {
                @Override public Void call() {
                    if (connectedStatus.get()) {
                        Boolean aquired = false;
                        try {
                            if (okToSend.tryAcquire(10, TimeUnit.SECONDS)){
                                aquired = true;
                                System.out.println("getReadStatus(): Sending ? command");
                                ultraOutput.writeBytes("?");
                                //ultraOutput.writeUTF("?");
                                ultraOutput.flush();
                                String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                if (result != null) {
                                    System.out.println("Reading Status : " + result);
                                    Boolean currentStatus = readingStatus.getValue();
                                    Boolean newStatus = false;
                                    if (result.substring(2, 3).startsWith("1")) newStatus = true;
                                    else newStatus = false;
                                    if (!Objects.equals(newStatus, currentStatus)){
                                        externalInitiated = true;
                                        Platform.runLater(() -> {
                                            if (result.substring(2, 3).startsWith("1")) readingStatus.setValue(Boolean.TRUE);
                                            else readingStatus.setValue(Boolean.FALSE);
                                        });
                                    }
                                    if (statusLabel.getText().equals("Starting Readers...") && newStatus) {
                                        Platform.runLater(() -> {
                                            statusLabel.setText("Connected: Waiting for a chip read...");
                                        });
                                    } else if (statusLabel.getText().equals("Connected: Readers stopped") && newStatus){
                                        Platform.runLater(() -> {
                                            statusLabel.setText("Connected: Waiting for a chip read...");
                                        });
                                    } else if (!newStatus){
                                        Platform.runLater(() -> {
                                            statusLabel.setText("Connected: Readers stopped");
                                        });
                                    }
                                } else {
                                // timeout
                                    System.out.println("Timeout with command '?'");
                                }
                            } else {
                                // timeout
                                System.out.println("Timeout waiting to send command '?'");
                            }
                        } catch (Exception ex) {
                            Logger.getLogger(PikaRFIDDirectReader.class.getName()).log(Level.SEVERE, null, ex);

                        } finally {
                            if (aquired) System.out.println("Relasing transmit lock");
                            if (aquired) okToSend.release();
                        }
                    }
                    return null;
                }
        };
        new Thread(ultraCommand).start();
    }
        
    @Override
    public BooleanProperty getReadingStatus() {
        return readingStatus; 
    }

    public void getSettings(CountDownLatch latch){
        
        Task ultraCommand = new Task<Void>() {
            @Override public Void call() {
                if (connectedStatus.get()) {
                    
                    Platform.runLater(() -> updateSettingsButton.setVisible(false));
                    
                    Boolean aquired = false;
                    try {
                        if (okToSend.tryAcquire(10, TimeUnit.SECONDS)){
                            aquired = true;
                            ultraSettings.clear();

                            String command = "r";
                            ultraOutput.writeBytes(command);

                            ultraOutput.flush();
                            String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                            if (result != null) {
                                // result is HH:MM:SS DD-MM-YYYY 
                                // and is NOT zero padded 
                                String[] dateTime = result.split(" ");
                                String[] d = dateTime[1].split("-");
                                if (d[1].length() == 1) d[1] = "0" + d[1];
                                if (d[0].length() == 1) d[0] = "0" + d[0];
                                String[] t = dateTime[0].split(":");
                                if (t[2].length() == 1) t[2] = "0" + t[2];
                                if (t[1].length() == 1) t[1] = "0" + t[1];
                                if (t[0].length() == 1) t[0] = "0" + t[0];
                                String time = t[0] + ":" + t[1] + ":" + t[2];
                                String date = d[2] + "-" + d[1] + "-" + d[0];
                                ultraClock.date = LocalDate.parse(date);
                                ultraClock.time = LocalTime.parse(time);
                                ultraClock.takenAt = LocalTime.now();
                                ultraClock.takenAt = ultraClock.takenAt.minusNanos(ultraClock.takenAt.getNano());

                            } else {
                            // timeout
                                System.out.println("Timeout with command 'r'");
                                return null;
                            }

                            

                            ultraOutput.flush();

                            ultraOutput.writeBytes("U");

                            ultraOutput.writeByte(10);

                            ultraOutput.flush();
                            do {
                                result = commandResultQueue.poll(5, TimeUnit.SECONDS);
                                if (result != null) {
                                    byte[] r = result.getBytes();
                                    if (result.length() > 2) {
                                        ultraSettings.put(String.format("%02X", r[1]), result.substring(2));
                                        System.out.println("Settings: " + String.format("%02X", r[1]) + " -> " +result.substring(2));
                                    } else if (result.equals("U2")) result= null; // 
                                }
                            } while(result != null);
                            
                            
                            //Beeper Volume Factor
                            try{
                                if (!ultraSettings.containsKey("21")) System.out.println("We don't know what the beeper volume is");
                                else {
                                    Integer volume =Integer.parseInt(ultraSettings.get("21"));
                                    Platform.runLater(() -> beeperVolumeChoiceBox.getSelectionModel().clearAndSelect(volume));
                                    System.out.println("Setting the volume to " + volume);
                                }
                            } catch (Exception e){
                                Platform.runLater(() -> beeperVolumeChoiceBox.getSelectionModel().selectFirst());
                                System.out.println("Beeper volume parse error!");
                            }
                            
                            //Gating Factor
                            try{
                                if (!ultraSettings.containsKey("1E")) System.out.println("We don't know what the gating is");
                                else {
                                    Integer gating =Integer.parseInt(ultraSettings.get("1E"));
                                    Platform.runLater(() -> gatingIntervalSpinner.getValueFactory().setValue(gating));
                                    System.out.println("Setting the gating factor to " + gating);
                                }
                            } catch (Exception e){
                                Platform.runLater(() -> gatingIntervalSpinner.getValueFactory().setValue(5));
                                System.out.println("Gating parse error, setting the gating factor to 5");
                            }
                            
                            // reader mode
                            try{
                                
                                if ("0".equals(ultraSettings.get("14"))) Platform.runLater(() -> reader1ModeChoiceBox.getSelectionModel().select("Start"));
                                else Platform.runLater(() -> reader1ModeChoiceBox.getSelectionModel().select("Finish"));
                            } catch (Exception e){
                                Platform.runLater(() -> reader1ModeChoiceBox.getSelectionModel().selectFirst());
                            }
                            
                            // Do we have a Joey or Ultra?
                            if ("255.255.255.255".equals(ultraSettings.get("1A"))) {
                                System.out.println("We have a Joey");
                                isSingleReader.set(true);
                                Platform.runLater(() -> isJoey.set(true));
                            } else {
                                Platform.runLater(() -> isJoey.set(false));
                                System.out.println("We have an Ultra");
                                // do we have an ultra-4 or ultra-8? 
                                // ping the ip in 1A to see if it exists. 
                                isSingleReader.set(!InetAddress.getByName(ultraSettings.get("1B")).isReachable(1000));
                                // if we failed, try one more time just in case we lost a packet
                                if (!isSingleReader.get()) isSingleReader.set(!InetAddress.getByName(ultraSettings.get("1B")).isReachable(1000));
                                if (!isSingleReader.get()) System.out.println("We have an ultra 8");
                                else System.out.println("We have an ultra 4");
                            }
                        } else {
                            // timeout
                            System.out.println("Timeout waiting to get Ultra Settings");
                            return null;
                        }
                    } catch (Exception ex) {
                        Logger.getLogger(PikaRFIDDirectReader.class.getName()).log(Level.SEVERE, null, ex);

                    } finally {
                        if (aquired) System.out.println("Relasing transmit lock");
                        if (aquired) okToSend.release();
                    }
                } else return null;


                ultraClock.tzHalfOffsetSupport = false;
        
                if (latch != null) latch.countDown();
                
                return null;
            }
        };
        new Thread(ultraCommand).start();
    }
    
    public void setClock(LocalDateTime time, Integer tz, Boolean gps){
        Task ultraCommand = new Task<Void>() {
                @Override public Void call() {
                    if (connectedStatus.get()) {
                        Boolean aquired = false;
                        try {
                            if (okToSend.tryAcquire(10, TimeUnit.SECONDS)){
                                aquired = true;
                                Boolean commit=false;
                                Boolean restartInterface=false;
                                if (time != null) {
                                    // t[0x20]HH:MM:SS DD-MM-YYYY  
                                    LocalDateTime adjTime = time.minusNanos(time.getNano()); // strip the nanoseconds
                                    String[] date = time.format(DateTimeFormatter.ISO_LOCAL_DATE).split("-"); // YYYY-MM-DD
                                    String newTime = adjTime.format(DateTimeFormatter.ISO_LOCAL_TIME) + " " +
                                        date[2] + "-" + date[1] + "-" + date[0]; // flip the ISO_LOCAL_DATE arouond
                                    String command = "t " + newTime;
                                    System.out.println("setClock(): Sending t command for a time of " + newTime);

                                    ultraOutput.writeBytes(command);
                                    //ultraOutput.writeUTF("?");
                                    ultraOutput.flush();
                                    String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    if (result != null) {
                                        commit=true;
                                    } else {
                                    // timeout
                                        System.out.println("Timeout with command 't'");
                                    }
                                } else {
                                    System.out.println("NULL time in setClock");
                                }
                                if (tz != null){
                                    System.out.println("setClock(): Sending tz (0x23) command");
                                    // t[0x20]HH:MM:SS DD-MM-YYYY  
                                    ultraOutput.flush();

                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(35);  // 0x23
                                    ultraOutput.writeBytes(tz.toString());
                                    ultraOutput.writeByte(255);
                                    
                                    ultraOutput.flush();
                                    String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    if (result != null) {
                                        ultraSettings.put("23",tz.toString());
                                        commit=true;
                                        restartInterface=true;
                                    } else {
                                    // timeout
                                        System.out.println("Timeout with command 'u0x23' timezone string");
                                    }
                                } else {
                                    System.out.println("NULL TZ in setClock()");
                                }
                                if (gps != null){
                                    System.out.println("setClock(): Sending auto-gps (0x22) command");
                                    // t[0x20]HH:MM:SS DD-MM-YYYY  
                                    ultraOutput.flush();

                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(34);  // 0x22
                                    if (gps) ultraOutput.writeByte(1);
                                    else ultraOutput.writeByte(0);
                                    ultraOutput.writeByte(255);
                                    
                                    ultraOutput.flush();
                                    String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    if (result != null) {
                                        commit=true;
                                        ultraSettings.put("22","1");
                                    } else {
                                    // timeout
                                        System.out.println("Timeout with command '0x22' to set the gps flag");
                                    }
                                }
                                if (commit){
                                    System.out.println("setClock(): Sending commit (u 0xFF 0xFF) command");
                                    // t[0x20]HH:MM:SS DD-MM-YYYY  
                                    ultraOutput.flush();

                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(255);
                                    ultraOutput.writeByte(255);
                                    ultraOutput.flush();
                                    String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    if (result != null) {

                                    } else {
                                    // timeout
                                        System.out.println("Timeout with command 't'");
                                    }
                                }
                                if (restartInterface){ // This will result in a disconnect
                                    System.out.println("setClock(): Sending reset interface (0x2D) command");
                                    
                                    ultraOutput.flush();

                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(45);
                                    ultraOutput.writeByte(255);
                                    ultraOutput.flush();
                                    
                                }
                            } else {
                                // timeout
                                System.out.println("Timeout waiting to send command '?'");
                            }
                        } catch (Exception ex) {
                            Logger.getLogger(PikaRFIDDirectReader.class.getName()).log(Level.SEVERE, null, ex);

                        } finally {
                            if (aquired) System.out.println("Relasing transmit lock");
                            if (aquired) okToSend.release();
                        }
                    }
                    return null;
                }
        };
        new Thread(ultraCommand).start();
        
    }
    
    @Override
    public Boolean chipIsBib() {
        return Boolean.FALSE; 
    }
    
    private void connect() {
        if (connectToUltra == true) return; // already connected
        connectToUltra = true;
        
        lastRead = -1;
        Task ultraConnection = new Task<Void>() {
                Boolean success = false;
                Boolean firstConnect = true;
                @Override public Void call() {
                    Boolean socketError = false;
                    Boolean readRetry = false;
                    while(connectToUltra) {
                        Platform.runLater(() -> {
                            statusLabel.setText("Connecting to " + ultraIP + "...");
                        });

                        //connectToUltra = false; // prevent looping if the connect fails
                        try (
                            Socket ultraSocket = new Socket(ultraIP, 23); 
                            InputStream input = ultraSocket.getInputStream();
                            OutputStream rawOutput = ultraSocket.getOutputStream();
                        ) {
                            connectToUltra = true; // we got here so we have a good connection
                            success = true;
                            ultraSocket.setSoTimeout(15000); // 15 seconds. In theory we get a voltage every 10
                            ultraOutput = new DataOutputStream(new BufferedOutputStream(rawOutput));
                            Platform.runLater(() -> {
                                connectedStatus.setValue(true);
                                statusLabel.setText("Connected to " + ultraIP);
                            });
                            int read = -255; 
                            String line = "";
                            while (read != 10) { // 1,Connected,<stuff>\n is sent on initial connect. 10 == \n
                                read = input.read();
                                System.out.println("Read: " + Character.toString ((char) read) + "  " + Integer.toHexString(0x100 | read).substring(1));
                            } 
                            System.out.println("Read connect string");
                            if (firstConnect) {
                                onConnectSetup();
                                firstConnect = false;
                            }
                            
                                while(connectToUltra) {
                                    read = -255; 
                                    line = "";
                                    try {
                                        while (read != 10 && connectToUltra) {
                                            read = input.read();
                                            readRetry = false;
                                            if (read == -1) {
                                                connectToUltra = false;
                                                System.out.println("End of stream!" + Integer.toHexString(read));
                                            } if (read != 10) {
                                                line = line +  Character.toString ((char) read);
                                            //    System.out.println("Read: " + Character.toString ((char) read) + "  " + Integer.toHexString(0x100 | read).substring(1));
                                            } else {
                                                processLine(line);
                                            }
                                        }
                                    } catch(java.net.SocketTimeoutException e){
                                        System.out.println("Socket Timeout Exception...");
                                        if (readRetry) {
                                            System.out.println("...2nd One in a row so we will bail");
                                            throw e;
                                        } else {
                                            System.out.println("...First one so let's ask for status");
                                            readRetry=true;
                                            getReadStatus();
                                        }
                                    }
                                }
                        } catch (Exception e) {
                            System.out.println(e);
                            if (connectToUltra){ 
                                socketError = true;
                                System.out.println("RFIDDirectReader Connection Exception: " + e.getMessage());
                                Platform.runLater(() -> {
                                    if (! success) { connectToUltra = false; connectedStatus.setValue(false);}
                                    statusLabel.setText("Error: " + e.getLocalizedMessage());
                                });
                                if (success) try {
                                    Thread.sleep(2000);
                                } catch (InterruptedException ex) {
                                    Logger.getLogger(PikaRFIDDirectReader.class.getName()).log(Level.SEVERE, null, ex);
                                } 
                            }
                        } finally {
                            if (!socketError){
                                Platform.runLater(() -> {
                                    connectedStatus.setValue(false);
                                    statusLabel.setText("Disconnected");
                                });
                            }
                        }
                    }
                    
                    return null; 
                }
            }; 
            ultraConnectionThread  = new Thread(ultraConnection);
            ultraConnectionThread.setName("Thread-Ultra-" + ultraIP);
            ultraConnectionThread.setDaemon(true);
            ultraConnectionThread.start();
        
    }
    
    private void disconnect(){
        statusLabel.setText("Disconecting from " + ultraIP + "...");
        connectToUltra = false;
    }
    
    private void processLine(String line) {
        System.out.println("Read Line: " + line);
        
        String type = "unknown";
        if (line.startsWith("0,")) type="chip";
        else if (line.startsWith("1,")) type="chip";
        else if (line.startsWith("V")) type="voltage";
        else if (line.startsWith("S")) type="status";
        else if (line.startsWith("U")) type="command";
        else if (line.startsWith("u")) type="command"; // general command
        else if (line.substring(0,8).matches("^\\d+:\\d+:\\d+.*")) type = "time"; //time ends with a special char
        else System.out.println("Unknown line: \"" + line + "\"");

        switch(type){
            case "chip": // chip time
                processRead(line);
                break;
            case "status": // status 
                System.out.println("Status: " + line);
                commandResultQueue.offer(line);
                break;
            case "voltage": // voltage
                System.out.println("Voltage: " + line);
                getReadStatus();
                break;
            case "time": // command response
                System.out.println("Time: " + line.substring(0,19));
                commandResultQueue.offer(line.substring(0,19));
                break;
            case "command": // command response
                System.out.println("Command response recieved");
                commandResultQueue.offer(line);
                break;
            default: // unknown command response
                System.out.println("Unknown: \"" + line.substring(0, 1) + "\" " + line);
                break;
        }
    }
    private void processRead(String r){
        System.out.println("Chip Read: " + r);
        String[] tokens = r.split(",", -1);
        // 0,11055,1170518701,698,1,-71,0,2,1,0000000000000000,0,29319
        // 0 -- junk
        // 1 -- chip
        // 2 -- time
        // 3 -- milis
        // 4 -- antenna / port
        // 5 -- RSSI (signal strength)
        // 6 -- is Rewind? (0 is live, 1 is memorex)
        // 7 -- Reader A or B (1 or 2)
        // 8 -- UltraID (zeroes)
        // 9 -- MTB Downhill Start Time
        // 10 -- LogID
        
        if (tokens.length < 12 ) {
            System.out.println("  Chip read is missing data: " + r);
            return;
        }
        
        String chip = tokens[1];
        String port = tokens[4];
        String reader = tokens[7];
        //String antenna = tokens[x];
        //String rewind = tokens[x];
        String rewind = tokens[6];
        String logNo = tokens[11];
        
        if (rewind.equals("0")) {
            int currentRead=Integer.parseInt(logNo);
            if (lastRead + 1 == currentRead || lastRead < 0 ) {
                //System.out.println("No missing reads: Last " + lastRead + " Current: " + logNo);
                lastRead = currentRead;
            }
            else {
                //System.out.println("Missing a read: Last " + lastRead + " Current: " + logNo);
                // auto-rewind
                rewind(lastRead,currentRead);
                lastRead = currentRead;
            }
        }
        
        System.out.println("  Chip: " + chip + " logNo: " + logNo);
        
        // make sure we have what we need...
        if (port.equals("0") && ! chip.equals("0")) { // invalid combo
            System.out.println("Non Start time: " + chip);
            return;
        } else if (!port.matches("[1234]") && !chip.equals("0")){
            System.out.println("Invalid Port: " + port);
            return;
        }
        
        //LocalDate origin = LocalDate.parse("1980-01-01",DateTimeFormatter.ISO_LOCAL_DATE); 
        //LocalDateTime read_ldt = LocalDateTime.of(origin, LocalTime.MIDNIGHT);
        Long seconds = Long.parseLong(tokens[2]);
        Long millis = Long.parseLong(tokens[3]);
        LocalDateTime read_ldt = EPOC.plusSeconds(seconds).plusNanos(millis * 1000000);
        
        LocalDateTime event_ldt = LocalDateTime.of(Event.getInstance().getLocalEventDate(), LocalTime.MIN);
        
        Duration timestamp = Duration.between(event_ldt,read_ldt);
        
        // if it is before the event date, just return
        if (timestamp.isNegative()) {
            String status = "Read Timestamp of " + timestamp + " is before the event date, ignoring";
            Platform.runLater(() -> {
                lastReadLabel.textProperty().setValue(status);
            });
            System.out.println(status);
        } else {
            RawTimeData rawTime = new RawTimeData();
            rawTime.setChip(chip);
            rawTime.setTimestampLong(timestamp.toNanos());
            String status = "Read of chip " + chip + " at " + DurationFormatter.durationToString(timestamp, 3) + " Reader: " + reader + " Port: " + port;
            Platform.runLater(() -> {
                lastReadLabel.textProperty().setValue(status);
            });
            if (statusLabel.getText().equals("Connected: Waiting for a chip read...") ){
                Platform.runLater(() -> {
                    statusLabel.setText("Connected: ");
                });
            }
            timingListener.processRead(rawTime); // process it
            //3,11274,0,"11:22:47.392",1,3
            if (saveToFile) {
                String date = read_ldt.format(DateTimeFormatter.ISO_LOCAL_DATE);
                Duration t = Duration.between(LocalTime.MIDNIGHT, read_ldt.toLocalTime());
                String time = DurationFormatter.durationToString(t, 3);
                if (t.minusHours(10).isNegative()) time = "0" + time; // zero pad the hours
                saveToFile(reader + "," + chip + "," + chip + ",\"" + date + " " + time + "\"," + reader + "," + port);
            }
        }
        
    }
    private void saveToFile(String line){
//        private Boolean saveToFile = false;
//        private String backupFile = null;
//        private PrintWriter outputFile = null;

        if (outputFile == null){
            File newFile = new File(backupFile).getAbsoluteFile();
            System.out.println("PikaRFIDDirectReader::saveToFile: opening " + newFile.getAbsolutePath());
            Boolean goodFile=false;
            try {
                // Not a directory and we can write to it _or_ we can create it
                if ((!newFile.isDirectory() && newFile.isFile() && newFile.canWrite())  || newFile.createNewFile()) {
                    outputFile = new PrintWriter(new FileOutputStream(newFile, true));
                }
            } catch (IOException ex) {
                //Logger.getLogger(PikaRFIDDirectReader.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        
        if (outputFile != null) {
            outputFile.println(line);
            if (outputFile.checkError()) System.out.println("PikaRFIDDirectReader::saveToFile: error writing to " + backupFile);
        }
        else System.out.println("PikaRFIDDirectReader::saveToFile: error opening file " + backupFile);


    }
    private void onConnectSetup() {
        // Get the reading status
        getReadStatus();
        
        // set the clock
        
//        Integer offset = TimeZone.getDefault().getOffset(System.currentTimeMillis())/3600000;
//        setClock(LocalDateTime.now(), offset, true);
        CountDownLatch latch = new CountDownLatch(1);
        getSettings(latch);
        clockIssuesCheck(latch);
                
        
}

    private void discover() {
        System.out.println("Starting discover...");
        ObservableList<Ultra> ultras = FXCollections.observableArrayList(); 
        BooleanProperty scanCompleted = new SimpleBooleanProperty(false);
        BooleanProperty dialogClosed = new SimpleBooleanProperty(false);
        // start a discovery task in a background thread
        Task ultraSearch = new Task<Void>() {
                @Override public Void call() {
                    
                    // This is ugly but it works
                    byte one = new Integer(1).byteValue();
                    byte zero = new Integer(0).byteValue();
                    byte[] packetData = {one,zero,zero,zero,zero,zero,zero,zero};
                    
                    // Find the server using UDP broadcast
                    //Loop while the dialog box is open
                    // UDP Broadcast code borrowed from https://demey.io/network-discovery-using-udp-broadcast/
                    // with a few modifications to protect the guilty and to bring it up to date
                    // (e.g., try-with-resources 
                    while (dialogClosed.not().get()) {
                        try(DatagramSocket broadcastSocket = new DatagramSocket()) {
                            broadcastSocket.setBroadcast(true);
                            // 2 second timeout for responses
                            broadcastSocket.setSoTimeout(2000);
                            
                            // Send a packet to 255.255.255.255 on port 2000
                            DatagramPacket probeDatagramPacket = new DatagramPacket(packetData, packetData.length, InetAddress.getByName("255.255.255.255"), 2000);
                            broadcastSocket.send(probeDatagramPacket);
                            
                            System.out.println("Sent UDP Broadcast to 255.255.255.255");
                            // Broadcast the message over all the network interfaces
                            
                            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
                            while (interfaces.hasMoreElements()) {
                              NetworkInterface networkInterface = interfaces.nextElement();

                              if (networkInterface.isLoopback() || !networkInterface.isUp()) {
                                continue; // Don't want to broadcast to the loopback interface
                              }

                              for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
                                InetAddress broadcast = interfaceAddress.getBroadcast();
                                if (broadcast == null) {
                                  continue;
                                }
                                // Send the broadcast package!
                                try {
                                  DatagramPacket sendPacket = new DatagramPacket(packetData, packetData.length, broadcast, 8888);
                                  broadcastSocket.send(sendPacket);
                                  System.out.println("Sent UDP Broadcast to " + broadcast.getHostAddress());
                                } catch (Exception e) {
                                }
                              }
                            }


                            //Wait for a response
                            try {
                                while (true) { // the socket timeout should stop this
                                    byte[] recvBuf = new byte[1500]; // mass overkill
                                    DatagramPacket receivePacket = new DatagramPacket(recvBuf, recvBuf.length);
                                    broadcastSocket.receive(receivePacket);
                                    
                                    
                                    String message = new String(receivePacket.getData()).trim();
                                    
                                    System.out.println("Ultra Finder Response: " + receivePacket.getAddress().getHostAddress() + " " + message);
                                    
                                    // parse the response string
                                    
                                    // Set the Ultra's IP
                                    Ultra u = new Ultra(receivePacket.getAddress().getHostAddress().toString());
                                    
                                    // Now the Type
                                    
                                    if (message.matches("(?i:.*JOEY.*)")) u.TYPE.set("Joey");
                                    if (message.matches("(?i:.*Ultra.*)")) u.TYPE.set("Ultra");
                                    
                                    // Finally the Rabbit MAC
                                    // dump the first 3 octets and save the last 3
                                    u.MAC.set(message.substring(message.indexOf(":")+7).toUpperCase( ));
                                    
                                    // If we have a new Ultra, save it. 
                                    if (!u.TYPE.getValueSafe().isEmpty() && !ultras.contains(u)) Platform.runLater(() -> {ultras.add(u);});
                                }
                            }catch (Exception ex){
                            }
                                                 
                        } catch (IOException ex) {
                          //Logger.getLogger(this.class.getName()).log(Level.SEVERE, null, ex);
                          System.out.println("oops...");
                        }
                    }
                    System.out.println("Done scanning for Ultras");
                    //Platform.runLater(() -> {scanCompleted.set(true);});
                                       

                    ultras.forEach(u -> {System.out.println("Found " + u.IP.getValueSafe());});
                    return null;
                }
        };
        Thread scanner = new Thread(ultraSearch);
        scanner.setDaemon(true);
        scanner.setName("Ultra Scanner");
        scanner.start();
        
        ProgressBar progress = new ProgressBar();
        progress.progressProperty().bind(ultraSearch.progressProperty());

        
        ListView<Ultra> ultraListView = new ListView();
        ultraListView.setItems(ultras);
       
        
        // open a dialog
        Dialog<Ultra> dialog = new Dialog();
        dialog.resizableProperty().set(true);
        dialog.getDialogPane().setMaxHeight(Screen.getPrimary().getVisualBounds().getHeight()-150);
        dialog.setTitle("Discover...");
        dialog.setHeaderText("Discover Local RFIDTiming Systems");
        ButtonType selectButtonType = new ButtonType("Select", ButtonBar.ButtonData.OK_DONE);
        dialog.getDialogPane().getButtonTypes().addAll(selectButtonType, ButtonType.CANCEL);
        
        // Create a scrollPane to put the tables and such in
        VBox mainVBox = new VBox();
        mainVBox.setPrefWidth(250);
        mainVBox.setStyle("-fx-font-size: 16px;"); // Make the scroll bar a bit larger
        VBox progressVBox = new VBox();
        progressVBox.setAlignment(Pos.CENTER);
        progressVBox.getChildren().add(new Label("Searching for Units..."));
        progressVBox.visibleProperty().bind(scanCompleted.not());
        progressVBox.managedProperty().bind(scanCompleted.not());
        progressVBox.getChildren().add(progress);
        Label foundCount = new Label();
        foundCount.textProperty().bind(Bindings.concat("Found ", Bindings.size(ultras).asString()));
        
        progressVBox.getChildren().add(foundCount);

        progress.setMaxWidth(500);
       
        progressVBox.setPrefHeight(175);

        VBox ultraListVBox = new VBox();
        ultraListVBox.setStyle("-fx-font-size: 16px;"); // Make everything normal again
        ultraListVBox.fillWidthProperty().set(true);
        ultraListVBox.setAlignment(Pos.CENTER_LEFT);
       
        Label selectLabel = new Label("Select an RFID source:");
        selectLabel.visibleProperty().bind(Bindings.size(ultras).isNotEqualTo(0));
        selectLabel.managedProperty().bind(Bindings.size(ultras).isNotEqualTo(0));
        ultraListVBox.getChildren().add(selectLabel);
        ultraListVBox.getChildren().add(ultraListView);
        
        Label notFound = new Label("No Ultras were found!.\nCheck network settings\nand try again.");
        notFound.visibleProperty().bind(Bindings.size(ultras).isEqualTo(0));
        notFound.managedProperty().bind(Bindings.size(ultras).isEqualTo(0));
        ultraListView.visibleProperty().bind(Bindings.size(ultras).greaterThanOrEqualTo(1));
        ultraListView.managedProperty().bind(Bindings.size(ultras).greaterThanOrEqualTo(1));
        
        ultraListVBox.setPrefHeight(1750);

        ultraListVBox.getChildren().add(notFound);
        ultraListVBox.visibleProperty().bind(scanCompleted);
        ultraListVBox.managedProperty().bind(scanCompleted);
        mainVBox.getChildren().add(progressVBox);
        mainVBox.getChildren().add(ultraListVBox);
        dialog.getDialogPane().setContent(mainVBox);
        
        scanCompleted.bind(Bindings.size(ultras).greaterThanOrEqualTo(1));
        
        // If they double click on an ultra, select it and close the dialog box
        ultraListView.setOnMouseClicked((MouseEvent click) -> {
            if (click.getClickCount() == 2) {
                dialog.setResult(ultraListView.getSelectionModel().getSelectedItem());
            }
        });
        
        dialog.getDialogPane().getScene().getWindow().sizeToScene();
        
        Node createButton = dialog.getDialogPane().lookupButton(selectButtonType);
        createButton.disableProperty().bind(ultraListView.getSelectionModel().selectedItemProperty().isNull());
        
        dialog.setResultConverter(dialogButton -> {
            if (dialogButton == selectButtonType) {
                return ultraListView.getSelectionModel().getSelectedItem();
            }
            return null;
        });

        Optional<Ultra> result = dialog.showAndWait();
        dialogClosed.set(true);

        
        if (result.isPresent()) {
            ultraIP = result.get().IP.getValueSafe();
            ultraIPTextField.setText(ultraIP);
            timingListener.setAttribute("RFIDDirect:ultra_ip", ultraIP);
            connectToggleSwitch.selectedProperty().set(true);
        }
        
    }

    private void rewind(){
        // open a dialog box 
        Dialog<RewindData> dialog = new Dialog();
        dialog.setTitle("Rewind");
        dialog.setHeaderText("Rewind timing data...");
        ButtonType rewindButtonType = new ButtonType("Rewind", ButtonBar.ButtonData.OK_DONE);
        dialog.getDialogPane().getButtonTypes().addAll(rewindButtonType, ButtonType.CANCEL);
        
        VBox rewindVBox = new VBox();
        rewindVBox.setStyle("-fx-font-size: 16px;");
        
        // start date / time
        HBox startHBox = new HBox();
        startHBox.setSpacing(5.0);
        Label startLabel = new Label("From:");
        startLabel.setMinWidth(40);
        DatePicker startDate = new DatePicker();
        TextField startTime = new TextField();
        startHBox.getChildren().addAll(startLabel,startDate,startTime);
        
        // end date / time
        HBox endHBox = new HBox();
        endHBox.setSpacing(5.0);
        Label endLabel = new Label("To:");
        endLabel.setMinWidth(40);
        DatePicker endDate = new DatePicker();
        TextField endTime = new TextField();
        endHBox.getChildren().addAll(endLabel,endDate,endTime);
        
        rewindVBox.getChildren().addAll(startHBox,endHBox);
        dialog.getDialogPane().setContent(rewindVBox);

        
        BooleanProperty startTimeOK = new SimpleBooleanProperty(false);
        BooleanProperty endTimeOK = new SimpleBooleanProperty(false);
        BooleanProperty allOK = new SimpleBooleanProperty(false);
       
        allOK.bind(Bindings.and(endTimeOK, startTimeOK));
        
        
        startTime.textProperty().addListener((observable, oldValue, newValue) -> {
            startTimeOK.setValue(false);
            if (DurationParser.parsable(newValue)) startTimeOK.setValue(Boolean.TRUE);
            if ( newValue.isEmpty() || newValue.matches("^[0-9]*(:?([0-5]?([0-9]?(:([0-5]?([0-9]?)?)?)?)?)?)?") ){
                System.out.println("Possiblely good start Time (newValue: " + newValue + ")");
            } else {
                Platform.runLater(() -> {
                    int c = startTime.getCaretPosition();
                    if (oldValue.length() > newValue.length()) c++;
                    else c--;
                    startTime.setText(oldValue);
                    startTime.positionCaret(c);
                });
                System.out.println("Bad start time (newValue: " + newValue + ")");
            }
        });
        endTime.textProperty().addListener((observable, oldValue, newValue) -> {
            endTimeOK.setValue(false);
            if (DurationParser.parsable(newValue)) endTimeOK.setValue(Boolean.TRUE);
            if ( newValue.isEmpty() || newValue.matches("^[0-9]*(:?([0-5]?([0-9]?(:([0-5]?([0-9]?)?)?)?)?)?)?") ){
                System.out.println("Possiblely good start Time (newValue: " + newValue + ")");
            } else {
                Platform.runLater(() -> {
                    int c = endTime.getCaretPosition();
                    if (oldValue.length() > newValue.length()) c++;
                    else c--;
                    endTime.setText(oldValue);
                    endTime.positionCaret(c);
                });
                System.out.println("Bad end time (newValue: " + newValue + ")");
            }
        });
        
        //Default to event date / 00:00 for the start time, event date 23:59:00 for the end time
        startDate.setValue(Event.getInstance().getLocalEventDate());
        startTime.setText("00:00:00");
        endDate.setValue(Event.getInstance().getLocalEventDate());
        endTime.setText("23:59:59");
        
        
        Node createButton = dialog.getDialogPane().lookupButton(rewindButtonType);
        createButton.disableProperty().bind(allOK.not());
        
        dialog.setResultConverter(dialogButton -> {
            if (dialogButton == rewindButtonType) {
                RewindData result = new RewindData();
                result.startDate = startDate.getValue();
                result.startTime = DurationParser.parse(startTime.getText());
                result.endDate = endDate.getValue();
                result.endTime = DurationParser.parse(endTime.getText());
                return result;
            }
            return null;
        });

        Optional<RewindData> result = dialog.showAndWait();

        
        if (result.isPresent()) {
            RewindData rwd= result.get();
            // convert the date/time to seconds since 1/1/1980
            
            Long startTimestamp = Duration.between(EPOC, LocalDateTime.of(rwd.startDate, LocalTime.ofSecondOfDay(rwd.startTime.getSeconds()))).getSeconds();
            Long endTimestamp = Duration.between(EPOC, LocalDateTime.of(rwd.endDate, LocalTime.ofSecondOfDay(rwd.endTime.getSeconds()))).getSeconds();
            
            System.out.println("Rewind from " + startTimestamp + " to " + endTimestamp);
            // issue the rewind command via a background thread
            
            Task ultraCommand = new Task<Void>() {
                @Override public Void call() {
                    if (connectedStatus.get()) {
                        Boolean aquired = false;
                        try {
                            if (okToSend.tryAcquire(10, TimeUnit.SECONDS)){
                                aquired=true;
                                String status = "Rewind from " + 
                                        rwd.startDate.format(DateTimeFormatter.ISO_LOCAL_DATE) + " " + startTime.getText() + " to " +
                                        rwd.endDate.format(DateTimeFormatter.ISO_LOCAL_DATE) + " " + endTime.getText();
                                
                                System.out.println(status);
                                Platform.runLater(() -> {
                                    statusLabel.setText(status);
                                });
                                ultraOutput.flush();
                                
                                // Send 8[0x00][0x00], like RFIDServer, not "800" per the manual
                                String command = "8";
                                command += Character.toString ((char) 0) ;
                                command += Character.toString ((char) 0) ;
                                command += startTimestamp.toString() ;
                                command += Character.toString ((char) 13) ;
                                command += endTimestamp.toString();
                                command += Character.toString ((char) 13) ;

                                ultraOutput.writeBytes(command);
                                ultraOutput.flush();

                            } else {
                                // timeout
                                System.out.println("Timeout with AutoRewind command");
                            }
                        } catch (Exception ex) {
                            Logger.getLogger(PikaRFIDDirectReader.class.getName()).log(Level.SEVERE, null, ex);

                        } finally {
                            if (aquired) okToSend.release();
                        }
                    }
                    return null;
                }
            };
            new Thread(ultraCommand).start();
            
            
            
        }
        
        
    }
    
    //Auto-Rewind
    private void rewind(Integer lastRead, Integer currentRead) {
           Task ultraCommand = new Task<Void>() {
                @Override public Void call() {
                    if (connectedStatus.get()) {
                        Boolean aquired = false;
                        try {
                            if (okToSend.tryAcquire(10, TimeUnit.SECONDS)){
                                aquired=true;
                                System.out.println("AutoRewind from " + lastRead + " to " + currentRead);

                                Platform.runLater(() -> {
                                    statusLabel.setText("AutoRewind from " + lastRead + " to " + currentRead);
                                });
                                
                                ultraOutput.flush();
                                
                                // Send 6[0x00][0x00], like RFIDServer, not "800" per the manual
                                String command = "6";
                                command += Character.toString ((char) 0) ;
                                command += Character.toString ((char) 0) ;
                                command += lastRead.toString() ;
                                command += Character.toString ((char) 13) ;
                                command += currentRead.toString();
                                command += Character.toString ((char) 13) ;

                                ultraOutput.writeBytes(command);
                                ultraOutput.flush();

                            } else {
                                // timeout
                                System.out.println("Timeout with AutoRewind command");
                            }
                        } catch (Exception ex) {
                            Logger.getLogger(PikaRFIDDirectReader.class.getName()).log(Level.SEVERE, null, ex);

                        } finally {
                            if (aquired) okToSend.release();
                        }
                    }
                    return null;
                }
            };
            new Thread(ultraCommand).start();
    }
    
    private void antennaDialog(){
        // Open a dialog box
        Dialog<Boolean> dialog = new Dialog();
        dialog.setTitle("Antenna settings");
        String type = "";
        if (isJoey.get()) type = "Joey";
        else if (isSingleReader.get()) type = "Ultra-4";
        else type = "Ultra-8";
        
        dialog.setHeaderText("Antenna and Power settings for " + type + " at "+ ultraIP);
        ButtonType saveButtonType = new ButtonType("Save", ButtonBar.ButtonData.OK_DONE);
        dialog.getDialogPane().getButtonTypes().addAll(saveButtonType, ButtonType.CANCEL);
        
        //VBox dialogVBox = new VBox();
        GridPane antennaGridPane = new GridPane();
        Map<String,CheckBox> antennaMap = new HashMap();
        
        antennaGridPane.setHgap(1);
        antennaGridPane.setVgap(4);
        
        Label portsLabel = new Label("Ports");
        antennaGridPane.add(portsLabel,1,0,4,1);
        GridPane.setHalignment(portsLabel, HPos.CENTER);
        
        // Power Option of 1 -> 32 (inclusive)
        PrefixSelectionComboBox<String> readerAPower = new PrefixSelectionComboBox();
        PrefixSelectionComboBox<String> readerBPower = new PrefixSelectionComboBox();
        for(Integer p = 32; p>0; p--){
            readerAPower.getItems().add(p.toString());
            readerBPower.getItems().add(p.toString());
        }
        
        // Reader 'A'
        for(int port = 1; port < 5; port++){
            antennaMap.put("A" + port,new CheckBox());
            System.out.println("Requesting ultraSetting " + String.format("%02x", 11 + port).toUpperCase());
            if (ultraSettings.containsKey(String.format("%02x", 11 + port).toUpperCase())) {
                if ("1".equals(ultraSettings.get(String.format("%02x", 11 + port).toUpperCase()))) antennaMap.get("A" + port).setSelected(true);
                else antennaMap.get("A" + port).setSelected(false);
            } else antennaMap.get("A" + port).setSelected(false);
            antennaGridPane.add(new Label(Integer.toString(port)),port,1);
            antennaGridPane.add(antennaMap.get("A" + port),port,2);
        } 
        if (!isJoey.get()) {
            antennaGridPane.add(new Label("Power"), 5,1);
            antennaGridPane.add(readerAPower, 5,2);
            if (ultraSettings.containsKey("18")){
                System.out.println("Reader A power set to " + ultraSettings.get("18"));
                readerAPower.getSelectionModel().select(ultraSettings.get("18"));
            } else readerAPower.getSelectionModel().selectFirst();
            
            antennaMap.put("ABackup",new CheckBox());
            if (ultraSettings.containsKey("26")) {
                if ("1".equals(ultraSettings.get("26"))) antennaMap.get("ABackup").setSelected(true);
                else antennaMap.get("ABackup").setSelected(false);
            } else antennaMap.get("ABackup").setSelected(false);
            antennaGridPane.add(new Label("  "),6,1);
            Label antBackupLlabel = new Label("Antenna 4\nis backup");
            antennaGridPane.add(antBackupLlabel,7,0,1,2);
            GridPane.setValignment(antBackupLlabel, VPos.BOTTOM);
            antennaGridPane.add(antennaMap.get("ABackup"),7,2);
        }
        
        // Reader 'B' but only if we have one...
        if (!isSingleReader.get()){
            Label readerALabel = new Label("Reader A ");
            antennaGridPane.add(readerALabel, 0, 2);
            Label readerBLabel = new Label("Reader B ");
            antennaGridPane.add(readerBLabel, 0, 3);
            for(int port = 1; port < 5; port++){
                antennaMap.put("B" + port,new CheckBox());
                System.out.println("Requesting ultraSetting " + Integer.toHexString(15 + port));
                if (ultraSettings.containsKey(Integer.toHexString(15 + port))) {
                    if ("1".equals(ultraSettings.get(Integer.toHexString(15 + port)))) antennaMap.get("B" + port).setSelected(true);
                    else antennaMap.get("B" + port).setSelected(false);
                } else antennaMap.get("B" + port).setSelected(false);
                antennaGridPane.add(antennaMap.get("B" + port),port,3);
            } 
            
            antennaGridPane.add(readerBPower, 5,3);
            if (ultraSettings.containsKey("19")){
                readerBPower.getSelectionModel().select(ultraSettings.get("19"));
                System.out.println("Reader B power set to " + ultraSettings.get("19"));
            } else readerBPower.getSelectionModel().selectFirst();
            
            antennaMap.put("BBackup",new CheckBox());
            if (ultraSettings.containsKey("27")) {
                if ("1".equals(ultraSettings.get("27"))) antennaMap.get("BBackup").setSelected(true);
                else antennaMap.get("BBackup").setSelected(false);
            } else antennaMap.get("BBackup").setSelected(false);
            antennaGridPane.add(antennaMap.get("BBackup"),7,3);
        }
        
        dialog.getDialogPane().setContent(antennaGridPane);
        
        // if good, save the settings
        dialog.setResultConverter(dialogButton -> {
            if (dialogButton == saveButtonType) {
                return Boolean.TRUE;
            }
            return null;
        });
        
        
        Optional result = dialog.showAndWait();
        
        if (result.isPresent()) {
            
            // If an Ultra-8
            if (!isSingleReader.get()) {
                // Reader B, 1 -> 4
                // Power Setting
                // Backup setting
            }
            
            // Play the task game
            Task ultraCommand = new Task<Void>() {
                @Override public Void call() {
                    if (connectedStatus.get()) {
                        Boolean aquired = false;
                        try {
                            if (okToSend.tryAcquire(10, TimeUnit.SECONDS)){
                                aquired = true;
                                Boolean commit=false;
                                Boolean restartInterface=false;
                                
                                
                                // 0x0C -> 0x0F Reader A, Antenna 1 -4
                                // 0x10 -> 0x13 Reader B, Antenna 1 -> 4
                                // 0x18 -> Reader A Power
                                // 0x26 -> Reader A Ant 4 as Backup (Ultra Only)
                                
                                // Reader A, 1 -> 4
                                for(int port = 1; port < 5; port++){
                                    String p = String.format("%02x", 11 + port).toUpperCase();
                                    if (!ultraSettings.containsKey(p) || 
                                        (antennaMap.get("A" + port).selectedProperty().get() && "0".equals(ultraSettings.get(p))) ||
                                        (!antennaMap.get("A" + port).selectedProperty().get() && "1".equals(ultraSettings.get(p)))){
                                        System.out.println("AntennaDialog(): Setting " + p + " to " + antennaMap.get("A" + port).selectedProperty().get());
                                        ultraOutput.flush();
                                        ultraOutput.writeBytes("u");
                                        ultraOutput.writeByte(11 + port);  // 0x0C -> 0x0F
                                        if (antennaMap.get("A" + port).selectedProperty().get()) ultraOutput.writeByte(1);
                                        else ultraOutput.writeByte(0);
                                        ultraOutput.writeByte(255);
                                        ultraOutput.flush();
                                        String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                        if (result != null) {
                                            commit=true;
                                        } else {
                                        // timeout
                                            System.out.println("Timeout with command '" + p + "' to enable/disable the antenna");
                                        }
                                    }
                                } 
                                if (!isJoey.get()) {
                                    // Power Setting
                                    if (!ultraSettings.containsKey("18") || 
                                            !ultraSettings.get("18").equals(readerAPower.getSelectionModel().getSelectedItem()) ){
                                        System.out.println("AntennaDialog(): Sending Reader A power (0x18) command to " + readerAPower.getSelectionModel().getSelectedItem());
                                        ultraOutput.flush();
                                        ultraOutput.writeBytes("u");
                                        ultraOutput.writeByte(24);  // 0x18
                                        ultraOutput.writeBytes(readerAPower.getSelectionModel().getSelectedItem());
                                        ultraOutput.writeByte(255);
                                        ultraOutput.flush();
                                        String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                        if (result != null) {
                                            commit=true;
                                        } else {
                                        // timeout
                                            System.out.println("Timeout with command '0x18' to set the Reader A port 4 backupflag");
                                        }
                                    }

                                    // If an Ultra backup setting
                                    if (!ultraSettings.containsKey("26") || 
                                            (antennaMap.get("ABackup").selectedProperty().get() && "0".equals(ultraSettings.get("26"))) ||
                                            (!antennaMap.get("ABackup").selectedProperty().get() && "1".equals(ultraSettings.get("26")))
                                        ){
                                        System.out.println("remoteDialog(): Sending Reader A port 4 backup (0x26) command");
                                        ultraOutput.flush();
                                        ultraOutput.writeBytes("u");
                                        ultraOutput.writeByte(38);  // 0x26
                                        if (antennaMap.get("ABackup").selectedProperty().get()) ultraOutput.writeByte(1);
                                        else ultraOutput.writeByte(0);
                                        ultraOutput.writeByte(255);
                                        ultraOutput.flush();
                                        String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                        if (result != null) {
                                            commit=true;
                                        } else {
                                        // timeout
                                            System.out.println("Timeout with command '0x18' to set the Reader A Power Command");
                                        }
                                    }
                                }
            
                                if (!isSingleReader.get()){
                                    
                                    // 0x10 -> 0x13 Reader B, Antenna 1 -> 4
                                    // 0x19 -> Reader B Power
                                    // 0x27 -> Reader B Ant 4 as Backup (Ultra Only)

                                    // Reader A, 1 -> 4
                                    for(int port = 1; port < 5; port++){
                                        String p = String.format("%02x", 15 + port).toUpperCase();
                                        if (!ultraSettings.containsKey(p) || 
                                            (antennaMap.get("B" + port).selectedProperty().get() && "0".equals(ultraSettings.get(p))) ||
                                            (!antennaMap.get("B" + port).selectedProperty().get() && "1".equals(ultraSettings.get(p)))){
                                            System.out.println("AntennaDialog(): Setting " + p + " to " + antennaMap.get("B" + port).selectedProperty().get());
                                            ultraOutput.flush();
                                            ultraOutput.writeBytes("u");
                                            ultraOutput.writeByte(15 + port);  // 0x0C -> 0x0F
                                            if (antennaMap.get("B" + port).selectedProperty().get()) ultraOutput.writeByte(1);
                                            else ultraOutput.writeByte(0);
                                            ultraOutput.writeByte(255);
                                            ultraOutput.flush();
                                            String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                            if (result != null) {
                                                commit=true;
                                            } else {
                                            // timeout
                                                System.out.println("Timeout with command '" + p + "' to enable/disable the antenna");
                                            }
                                        }

                                    } 
                                    if (!ultraSettings.containsKey("19") || 
                                        !ultraSettings.get("19").equals(readerBPower.getSelectionModel().getSelectedItem()) ){
                                        System.out.println("remoteDialog(): Sending Reader B power (0x19) command to " + readerBPower.getSelectionModel().getSelectedItem());
                                        ultraOutput.flush();
                                        ultraOutput.writeBytes("u");
                                        ultraOutput.writeByte(25);  // 0x19
                                        ultraOutput.writeBytes(readerBPower.getSelectionModel().getSelectedItem());
                                        ultraOutput.writeByte(255);
                                        ultraOutput.flush();
                                        String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                        if (result != null) {
                                            commit=true;
                                        } else {
                                        // timeout
                                            System.out.println("Timeout with command '0x19' to set the Reader B Power Command");
                                        }
                                    }

                                    // If an Ultra backup setting
                                    if (!ultraSettings.containsKey("27") || 
                                            (antennaMap.get("BBackup").selectedProperty().get() && "0".equals(ultraSettings.get("27"))) ||
                                            (!antennaMap.get("BBackup").selectedProperty().get() && "1".equals(ultraSettings.get("27")))
                                        ){
                                        System.out.println("remoteDialog(): Sending Reader B port 4 backup (0x27) command");
                                        ultraOutput.flush();
                                        ultraOutput.writeBytes("u");
                                        ultraOutput.writeByte(39);  // 0x27
                                        if (antennaMap.get("BBackup").selectedProperty().get()) ultraOutput.writeByte(1);
                                        else ultraOutput.writeByte(0);
                                        ultraOutput.writeByte(255);
                                        ultraOutput.flush();
                                        String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                        if (result != null) {
                                            commit=true;

                                        } else {
                                        // timeout
                                            System.out.println("Timeout with command '0x27' to set the Reader B port 4 backup flag");
                                        }
                                    }
                                }
                                
                                if (commit){
                                    System.out.println("remoteDialog(): Sending commit (u 0xFF 0xFF) command");
                                    ultraOutput.flush();

                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(255);
                                    ultraOutput.writeByte(255);
                                    ultraOutput.flush();
                                    String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    if (result != null) {
                                        restartInterface = true;

                                        // Reader A
                                        for(int port = 1; port < 5; port++){
                                            String p = String.format("%02x", 11 + port).toUpperCase();
                                            if (antennaMap.get("A" + port).selectedProperty().get()) ultraSettings.put(p,"1");
                                            else ultraSettings.put(p,"0");
                                        } 
                                        if (!isJoey.get()){
                                            ultraSettings.put("18",readerAPower.getSelectionModel().getSelectedItem());
                                            if (antennaMap.get("ABackup").selectedProperty().get()) ultraSettings.put("26","1");
                                            else ultraSettings.put("26","0");
                                        }
                                        if (!isSingleReader.get()){
                                            // Reader B
                                            for(int port = 1; port < 5; port++){
                                                String p = String.format("%02x", 15 + port).toUpperCase();
                                                if (antennaMap.get("B" + port).selectedProperty().get()) ultraSettings.put(p,"1");
                                                else ultraSettings.put(p,"0");
                                            } 
                                                ultraSettings.put("19",readerBPower.getSelectionModel().getSelectedItem());
                                                if (antennaMap.get("BBackup").selectedProperty().get()) ultraSettings.put("27","1");
                                                else ultraSettings.put("27","0");
                                        }
                                        
                                    } else {
                                    // timeout
                                        System.out.println("Timeout with command 'u[0xFF][0xFF]'");
                                    }
                                }
                                if (restartInterface){ // This will result in a disconnect
                                    System.out.println("Sending reset interface (0x2D) command");
                                    ultraOutput.flush();
                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(45);
                                    ultraOutput.writeByte(255);
                                    ultraOutput.flush();
                                    
                                }
                            } else {
                                // timeout
                                System.out.println("Timeout waiting to send command '?'");
                            }
                        } catch (Exception ex) {
                            Logger.getLogger(PikaRFIDDirectReader.class.getName()).log(Level.SEVERE, null, ex);

                        } finally {
                            if (aquired) System.out.println("Relasing transmit lock");
                            if (aquired) okToSend.release();
                        }
                    }
                    return null;
                }
        };
        new Thread(ultraCommand).start();
            
            
            
        }
        
        
    }
    private void remoteDialog(){
        // Settings 
        // 0x01:Remote Type (0 = off, 1 = gprs, 2 = lan)
        // 0x02: IP of remote server for GPRS. Send in hex? See 0x29
        // 0x03: Port for remote server
        // 0x04: APN name
        // 0x05: APPN User
        // 0x06: APN password
        // 0x29: URL for http uploading (er, IP)
        //       173.192.106.122 for USA1.RFIDTiming.com
        //       82.113.145.195 for EUROPE1.RFIDTiming.com
        // 0x2A: Gateway for LAN
        // 0x2B: DNS Server for (0x29) or blank if IP
        // 0x2C: ??? -- looks like our non-dhcp IP for joey's 
        // 0x2E: Enable / Disable sending to remote: 0 or 1 
        
        // Yeah, yeah, we should return an optional with an object that has the
        // settings in it. Or a boolean and then we can just yank them from 
        // the nodes and call it a good day. 
        
        // Open a dialog box
        Dialog<Boolean> dialog = new Dialog();
        dialog.setTitle("Remote settings");
        dialog.setHeaderText("Configure the remote settings for " + ultraIP);
        ButtonType setButtonType = new ButtonType("Save", ButtonBar.ButtonData.OK_DONE);
        dialog.getDialogPane().getButtonTypes().addAll(setButtonType, ButtonType.CANCEL);
        Node saveButton = dialog.getDialogPane().lookupButton(setButtonType);
        
        ToggleSwitch enableRemoteToggleSwitch = new ToggleSwitch("Send data to remote Server");
        if (ultraSettings.containsKey("2E") && "1".equals(ultraSettings.get("2E"))) enableRemoteToggleSwitch.setSelected(true);
        else enableRemoteToggleSwitch.setSelected(false);
        
        // set the gprsChoiceBox
        // The options are in a specific order to reflect what the Ultra/Joey wants (0 = off, 1 = gprs, 2 = lan)
        Label gprsLabel = new Label("Connection Type");
        ChoiceBox<String> gprsChoiceBox = new ChoiceBox(FXCollections.observableArrayList("Off", "Internal Modem", "LAN"));
        HBox typeHBox = new HBox();
        typeHBox.setSpacing(4);
        typeHBox.getChildren().setAll(gprsLabel,gprsChoiceBox);
        
        
        // Port
        Label portLabel = new Label("Remote Server Port");
        TextField portTextField = new TextField();
        portTextField.textProperty().addListener((observable, oldValue, newValue) -> {
            if(newValue == null || newValue.isEmpty()) return;
            if (!newValue.matches("\\d+")) {
                    Platform.runLater(() -> { 
                    int c = portTextField.getCaretPosition();
                    portTextField.setText(oldValue);
                    portTextField.positionCaret(c);
                }); 
            }
        });
        
        if (ultraSettings.containsKey("03")) portTextField.setText(ultraSettings.get("03"));
        else portTextField.setText("11111");
        HBox portHBox = new HBox();
        portHBox.setSpacing(4);
        portHBox.getChildren().setAll(portLabel,portTextField);
        
        //Server
        String customIP = "";
        Label serverLabel = new Label("Remote Server");
        ChoiceBox<String> serverChoiceBox = new ChoiceBox(FXCollections.observableArrayList("USA1.RFIDTiming.com", "EUROPE1.RFIDTiming.com", "Custom"));
        Label customServerLabel = new Label("Remote Server IP");
        TextField customServerTextField = new TextField();
        VBox serverVBox = new VBox();
        serverVBox.setSpacing(4);
        HBox serverHBox = new HBox();
        serverHBox.setSpacing(4);
        
        serverHBox.getChildren().setAll(serverLabel,serverChoiceBox);
        
        HBox customServerHBox = new HBox();
        customServerHBox.setSpacing(4);
        customServerHBox.getChildren().setAll(customServerLabel,customServerTextField);
        serverVBox.getChildren().setAll(serverHBox,customServerHBox);
        
        serverChoiceBox.getSelectionModel().selectedItemProperty().addListener((ov, oldType, newType) -> {
            if ("Custom".equals(newType)){
                customServerHBox.setVisible(true);
                customServerHBox.setManaged(true);
                saveButton.disableProperty().set(true);
                dialog.getDialogPane().getScene().getWindow().sizeToScene();
            } else {
                customServerHBox.setVisible(false);
                customServerHBox.setManaged(false);
                saveButton.disableProperty().set(false);
                dialog.getDialogPane().getScene().getWindow().sizeToScene();
            }
        });
        
        String currentIP ="";
        //       173.192.106.122 for USA1.RFIDTiming.com
        //       82.113.145.195 for EUROPE1.RFIDTiming.com
        try {
            Integer remoteInt = Integer.parseInt(ultraSettings.get("01"));
            
            if (remoteInt == 1 && ultraSettings.containsKey("02")) currentIP = ultraSettings.get("02");
            if (remoteInt == 2 && ultraSettings.containsKey("29")) currentIP = ultraSettings.get("29");
            else currentIP = "173.192.106.122";
            
        } catch (Exception e){
            serverChoiceBox.getSelectionModel().selectFirst();
            currentIP = "173.192.106.122";
        }
        System.out.println("Current IP: \"" + currentIP + "\"");
        if ("173.192.106.122".equals(currentIP)) serverChoiceBox.getSelectionModel().select(0);
        else if ("82.113.145.195".equals(currentIP)) serverChoiceBox.getSelectionModel().select(1);
        else {
            serverChoiceBox.getSelectionModel().selectLast();
            customServerTextField.setText(currentIP);
        }
        
        
        // This is way more complicated than it should be...
        customServerTextField.textProperty().addListener((observable, oldValue, newValue) -> {
            Boolean revert = false;
            if (!"Custom".equals(serverChoiceBox.getSelectionModel().getSelectedItem())) {
                saveButton.disableProperty().set(false);
                return;
            }
            if (newValue.isEmpty()) saveButton.disableProperty().set(true);
            
            if (newValue.matches("[\\d\\.]+")) { // numbers and dots only for the inital pass
                String octets[] = newValue.split("\\.");
                if (octets.length != 4) saveButton.disableProperty().set(true);
                if (octets.length > 4){ // too many octets, cut a few and it will be fine
                    revert = true;
                } else {
                    Boolean validIP = true;
                    for(String octet: octets) {
                        try {
                            Integer o = Integer.parseInt(octet);
                            System.out.println("Octet : " + o);
                            if (o > 255) {
                                validIP = false;
                                revert = true;
                            }
                        } catch (Exception e){
                            System.out.println("Octet Exception: " + e.getLocalizedMessage());

                            validIP = false;
                        }
                    }
                    if (validIP && octets.length == 4) {
                        System.out.println("Valid IP : " + customIP);
                        saveButton.disableProperty().set(false);
                    }
                    else{
                        saveButton.disableProperty().set(true);
                    }
                }
            } else { //just say no
                revert = true;
            }
            if (revert) {
                    Platform.runLater(() -> { 
                    int c = customServerTextField.getCaretPosition();
                    customServerTextField.setText(oldValue);
                    customServerTextField.positionCaret(c);
                }); 
            }
        });
        
        // LAN
        Label gatewayLabel = new Label("Gateway");
        TextField gatewayTextField = new TextField();
        if (ultraSettings.containsKey("2A")) gatewayTextField.setText(ultraSettings.get("2A"));

        HBox gatewayHBox = new HBox();
        gatewayHBox.setSpacing(4);
        gatewayHBox.getChildren().setAll(gatewayLabel,gatewayTextField);
        
        // 3G modem
        Label apnNameLabel = new Label("APN Name");
        TextField apnNameTextField = new TextField();
        if (ultraSettings.containsKey("04")) apnNameTextField.setText(ultraSettings.get("04"));
        HBox apnNameHBox = new HBox();
        apnNameHBox.setSpacing(4);
        apnNameHBox.getChildren().setAll(apnNameLabel,apnNameTextField);
        
        Label apnUserNameLabel = new Label("APN Username");
        TextField apnUserNameTextField = new TextField();
        if (ultraSettings.containsKey("05")) apnUserNameTextField.setText(ultraSettings.get("05"));
        HBox apnUserNameHBox = new HBox();
        apnUserNameHBox.setSpacing(4);
        apnUserNameHBox.getChildren().setAll(apnUserNameLabel,apnUserNameTextField);
        
        Label apnPasswordLabel = new Label("APN Password");
        TextField apnPasswordTextField = new TextField();
        if (ultraSettings.containsKey("06")) apnPasswordTextField.setText(ultraSettings.get("06"));
        HBox apnPasswordHBox = new HBox();
        apnPasswordHBox.setSpacing(4);
        apnPasswordHBox.getChildren().setAll(apnPasswordLabel,apnPasswordTextField);
        
        VBox apnVBox = new VBox();
        apnVBox.setSpacing(4);
        apnVBox.getChildren().setAll(apnNameHBox,apnUserNameHBox,apnPasswordHBox);
        
        VBox configVBox = new VBox();
        configVBox.setSpacing(4);
        configVBox.getChildren().setAll(portHBox,serverVBox,gatewayHBox,apnVBox);
        
        gprsChoiceBox.getSelectionModel().selectedItemProperty().addListener((ov, oldType, newType) -> {
            if (null != newType) //("Off", "Internal Modem", "LAN")
            switch (newType) {
                case "Off":
                    // Hide the config options VBox
                    configVBox.setVisible(false);
                    configVBox.setManaged(false);
                    // resize the dialog box
                    dialog.getDialogPane().getScene().getWindow().sizeToScene();
                    break;
                case "Internal Modem":
                    // Show the config options VBox
                    configVBox.setVisible(true);
                    configVBox.setManaged(true);
                    // Show APN related stuff
                    apnVBox.setVisible(true);
                    apnVBox.setManaged(true);
                    // hide gateway
                    gatewayHBox.setVisible(false);
                    gatewayHBox.setManaged(false);
                    // resize the dialog box
                    dialog.getDialogPane().getScene().getWindow().sizeToScene();
                    break;
                case "LAN":
                    // Show the config options VBox
                    configVBox.setVisible(true);
                    configVBox.setManaged(true);
                    // show gateway
                    gatewayHBox.setVisible(true);
                    gatewayHBox.setManaged(true);
                    // Hide APN related stuff
                    apnVBox.setVisible(false);
                    apnVBox.setManaged(false);
                    
                    // resize the dialog box
                    dialog.getDialogPane().getScene().getWindow().sizeToScene();
                    break;
                default:
                    gprsChoiceBox.getSelectionModel().selectFirst();
            }
        
        });
        
        
        try {
            gprsChoiceBox.getSelectionModel().select(Integer.parseInt(ultraSettings.get("01")));
        } catch (Exception e){
            gprsChoiceBox.getSelectionModel().selectFirst();
        }
        
        VBox dialogVBox = new VBox();
        dialogVBox.setSpacing(4);
        dialogVBox.getChildren().setAll(enableRemoteToggleSwitch,typeHBox,configVBox);
        
        dialog.getDialogPane().setContent(dialogVBox);
        
        // if good, save the settings
        dialog.setResultConverter(dialogButton -> {
            if (dialogButton == setButtonType) {
                return Boolean.TRUE;
            }
            return null;
        });
        
        double labelWidth = 110;
        portLabel.setPrefWidth(labelWidth);
        gprsLabel.setPrefWidth(labelWidth);
        serverLabel.setPrefWidth(labelWidth);
        customServerLabel.setPrefWidth(labelWidth);
        gatewayLabel.setPrefWidth(labelWidth);
        apnNameLabel.setPrefWidth(labelWidth);
        apnUserNameLabel.setPrefWidth(labelWidth);
        apnPasswordLabel.setPrefWidth(labelWidth);

        Optional<Boolean> result = dialog.showAndWait();

        if (result.isPresent()) {
            // Play the task game
            Task ultraCommand = new Task<Void>() {
                @Override public Void call() {
                    if (connectedStatus.get()) {
                        Boolean aquired = false;
                        try {
                            if (okToSend.tryAcquire(10, TimeUnit.SECONDS)){
                                aquired = true;
                                Boolean commit=false;
                                Boolean restartInterface=false;
                                
                                
                                // 0x29: URL for http uploading (er, IP)
                                //       173.192.106.122 for USA1.RFIDTiming.com
                                //       82.113.145.195 for EUROPE1.RFIDTiming.com
                                // 0x2A: Gateway for LAN
                                // 0x2B: DNS Server for (0x29) or blank if IP
                                
                                
                                // 0x2E: Enable / Disable sending to remote: 0 or 1 
                                if (!ultraSettings.containsKey("2E") || 
                                        (enableRemoteToggleSwitch.selectedProperty().get() && "0".equals(ultraSettings.get("2E"))) ||
                                        (!enableRemoteToggleSwitch.selectedProperty().get() && "1".equals(ultraSettings.get("2E")))
                                    ){
                                    System.out.println("remoteDialog(): Sending enable/disable (0x2E) command");
                                    ultraOutput.flush();
                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(46);  // 0x2E
                                    if (enableRemoteToggleSwitch.selectedProperty().get()) ultraOutput.writeByte(1);
                                    else ultraOutput.writeByte(0);
                                    ultraOutput.writeByte(255);
                                    
                                    ultraOutput.flush();
                                    String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    if (result != null) {
                                        commit=true;
                                        
                                    } else {
                                    // timeout
                                        System.out.println("Timeout with command '0x2E' to set the send to remote flag");
                                    }
                                }
                                
                                // 0x01:Remote Type (0 = off, 1 = gprs, 2 = lan)
                                if (!ultraSettings.containsKey("01") || 
                                        !Integer.toString(gprsChoiceBox.getSelectionModel().getSelectedIndex()).equals(ultraSettings.get("01"))
                                    ){
                                    System.out.println("remoteDialog(): Sending Remote Type (0x01) command to " + Integer.toString(gprsChoiceBox.getSelectionModel().getSelectedIndex()));
                                    ultraOutput.flush();
                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(1);  // 0x01
                                    ultraOutput.writeByte(gprsChoiceBox.getSelectionModel().getSelectedIndex());
                                    ultraOutput.writeByte(255);
                                    
                                    ultraOutput.flush();
                                    String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    if (result != null) {
                                        commit=true;
                                        
                                    } else {
                                    // timeout
                                        System.out.println("Timeout with command '0x01' to set the remote type flag");
                                    }
                                }
                                
                                // 0x03: Port for remote server
                                if (!ultraSettings.containsKey("03") || 
                                        !portTextField.getText().equals(ultraSettings.get("03"))
                                    ){
                                    System.out.println("remoteDialog(): Sending Remote Port (0x03) command to " + portTextField.getText());
                                    ultraOutput.flush();
                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(3);  // 0x03
                                    ultraOutput.writeBytes(portTextField.getText());
                                    ultraOutput.writeByte(255);
                                    ultraOutput.flush();
                                    String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    if (result != null) {
                                        commit=true;
                                    } else {
                                    // timeout
                                        System.out.println("Timeout with command '0x03' to set the remote type flag");
                                    }
                                }

                                // 0x04: APN name
                                if (!ultraSettings.containsKey("04") || 
                                        !apnNameTextField.getText().equals(ultraSettings.get("04"))
                                    ){
                                    System.out.println("remoteDialog(): Sending Remote Port (0x04) command to " + apnNameTextField.getText());
                                    ultraOutput.flush();
                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(4);  // 0x03
                                    ultraOutput.writeBytes(apnNameTextField.getText());
                                    ultraOutput.writeByte(255);
                                    ultraOutput.flush();
                                    String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    if (result != null) {
                                        commit=true;
                                    } else {
                                    // timeout
                                        System.out.println("Timeout with command '0x04' to set the rapn name");
                                    }
                                }
                                
                                // 0x05: APPN user
                                if (!ultraSettings.containsKey("05") || 
                                        !apnUserNameTextField.getText().equals(ultraSettings.get("05"))
                                    ){
                                    System.out.println("remoteDialog(): Sending apn UserName (0x05) command to " + apnUserNameTextField.getText());
                                    ultraOutput.flush();
                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(5);  // 0x03
                                    ultraOutput.writeBytes(apnUserNameTextField.getText());
                                    ultraOutput.writeByte(255);
                                    ultraOutput.flush();
                                    String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    if (result != null) {
                                        commit=true;
                                    } else {
                                    // timeout
                                        System.out.println("Timeout with command '0x05' to set the rapn name");
                                    }
                                }
                                // 0x06: APN password
                                if (!ultraSettings.containsKey("06") || 
                                        !apnPasswordTextField.getText().equals(ultraSettings.get("06"))
                                    ){
                                    System.out.println("remoteDialog(): Sending apnPassword (0x06) command to " + apnPasswordTextField.getText());
                                    ultraOutput.flush();
                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(6);  // 0x03
                                    ultraOutput.writeBytes(apnPasswordTextField.getText());
                                    ultraOutput.writeByte(255);
                                    ultraOutput.flush();
                                    String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    if (result != null) {
                                        commit=true;
                                    } else {
                                    // timeout
                                        System.out.println("Timeout with command '0x06' to set the rapn name");
                                    }
                                }
                                
                                // 0x02: IP of remote server for GPRS. Send in hex? See 0x29
                                // 0x29: URL for http uploading (er, IP)
                                //       173.192.106.122 for USA1.RFIDTiming.com
                                //       82.113.145.195 for EUROPE1.RFIDTiming.com
                                // We set both 0x02 and 0x29 to the same effective value
                                // to prevent really odd things
                                
                                String newIP = "";
                                switch (serverChoiceBox.getSelectionModel().getSelectedIndex()) {
                                    case 0:
                                        newIP="173.192.106.122";
                                        break;
                                    case 1:
                                        newIP="82.113.145.195";
                                        break;
                                    default:
                                        newIP=customServerTextField.getText();
                                        break;
                                }
                                // 0x02: URL for http uploading (er, IP)
                                if ((!ultraSettings.containsKey("02") || 
                                        !newIP.equals(ultraSettings.get("02")) ) && 
                                        newIP.matches("\\d+\\.\\d+\\.\\d+\\.\\d+")
                                    ){
                                    System.out.println("remoteDialog(): Sending Remote IP (0x02) command to " + newIP);
                                    ultraOutput.flush();
                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(2);  // 0x02
                                    String[] ipBytes = newIP.split("\\.");
                                    for(String o: ipBytes){
                                        ultraOutput.writeByte(Integer.parseUnsignedInt(o));
                                    }
                                    ultraOutput.writeByte(255);
                                    ultraOutput.flush();
                                    String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    if (result != null) {
                                        commit=true;
                                    } else {
                                    // timeout
                                        System.out.println("Timeout with command '0x02' to set the remote server");
                                    }
                                }
                                // 0x29: URL for http uploading (er, IP)
                                if (!ultraSettings.containsKey("29") || 
                                        !newIP.equals(ultraSettings.get("29"))
                                    ){
                                    System.out.println("remoteDialog(): Sending Remote IP (0x29) command to " + newIP);
                                    ultraOutput.flush();
                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(41);  // 0x29
                                    ultraOutput.writeBytes(newIP);
                                    ultraOutput.writeByte(255);
                                    ultraOutput.flush();
                                    String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    if (result != null) {
                                        commit=true;
                                    } else {
                                    // timeout
                                        System.out.println("Timeout with command '0x29' to set the rapn name");
                                    }
                                }
                                        
                                // 0x2A: Gateway for LAN
                                if ((!ultraSettings.containsKey("2A") || 
                                        !gatewayTextField.getText().equals(ultraSettings.get("2A")) ) && 
                                        gatewayTextField.getText().matches("\\d+\\.\\d+\\.\\d+\\.\\d+")
                                    ){
                                    System.out.println("remoteDialog(): Sending Remote IP (0x2A) command to " + gatewayTextField.getText());
                                    ultraOutput.flush();
                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(2);  // 0x02
                                    String[] ipBytes = gatewayTextField.getText().split("\\.");
                                    for(String o: ipBytes){
                                        ultraOutput.writeByte(Integer.parseUnsignedInt(o));
                                    }
                                    ultraOutput.writeByte(255);
                                    ultraOutput.flush();
                                    String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    if (result != null) {
                                        commit=true;
                                    } else {
                                    // timeout
                                        System.out.println("Timeout with command '0x02' to set the remote server");
                                    }
                                }
                                if (commit){
                                    System.out.println("remoteDialog(): Sending commit (u 0xFF 0xFF) command");
                                    ultraOutput.flush();

                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(255);
                                    ultraOutput.writeByte(255);
                                    ultraOutput.flush();
                                    String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    if (result != null) {
                                        // 0x2E: Enable / Disable sending to remote: 0 or 1 
                                        if (enableRemoteToggleSwitch.selectedProperty().get()) ultraSettings.put("2E","1");
                                        else ultraSettings.put("2E","0");
                                        
                                        // 0x01:Remote Type (0 = off, 1 = gprs, 2 = lan)
                                        ultraSettings.put("01",Integer.toString(gprsChoiceBox.getSelectionModel().getSelectedIndex()));
                                        
                                        // 0x03: Port for remote server
                                        ultraSettings.put("03",portTextField.getText());
                                        
                                        // 0x04: APN name
                                        ultraSettings.put("04",apnNameTextField.getText());
                                        // 0x05: APPN user
                                        ultraSettings.put("05",apnUserNameTextField.getText());
                                        // 0x06: APN password
                                        ultraSettings.put("06",apnPasswordTextField.getText());
                                        
                                        // 0x29: URL for http uploading (er, IP)
                                        if (newIP.matches("\\d+\\.\\d+\\.\\d+\\.\\d+")) ultraSettings.put("29",newIP);
                                        // 0x02: IP of remote server for GPRS. Send in hex? See 0x29
                                        if (newIP.matches("\\d+\\.\\d+\\.\\d+\\.\\d+")) ultraSettings.put("02",newIP);
                                        
                                        // 0x2A: Gateway for LAN
                                        if (gatewayTextField.getText().matches("\\d+\\.\\d+\\.\\d+\\.\\d+")) ultraSettings.put("2A",gatewayTextField.getText());

                                    } else {
                                    // timeout
                                        System.out.println("Timeout with command 't'");
                                    }
                                }
                                if (restartInterface){ // This will result in a disconnect
                                    System.out.println("setClock(): Sending reset interface (0x2D) command");
                                    
                                    ultraOutput.flush();

                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(45);
                                    ultraOutput.writeByte(255);
                                    ultraOutput.flush();
                                    
                                }
                            } else {
                                // timeout
                                System.out.println("Timeout waiting to send command '?'");
                            }
                        } catch (Exception ex) {
                            Logger.getLogger(PikaRFIDDirectReader.class.getName()).log(Level.SEVERE, null, ex);

                        } finally {
                            if (aquired) System.out.println("Relasing transmit lock");
                            if (aquired) okToSend.release();
                        }
                    }
                    return null;
                }
        };
        new Thread(ultraCommand).start();
            
        }
    }
    
    private void setClockDialog(){
        Integer localTZ = TimeZone.getDefault().getOffset(System.currentTimeMillis())/3600000;
        Integer ultraTZ = Integer.parseInt(ultraSettings.get("23"));

        // open a dialog box 
        Dialog<Boolean> dialog = new Dialog();
        dialog.setTitle("Set Ultra Clock");
        dialog.setHeaderText("Set the clock for " + ultraIP);
        ButtonType setButtonType = new ButtonType("Set", ButtonBar.ButtonData.OK_DONE);
        dialog.getDialogPane().getButtonTypes().addAll(setButtonType, ButtonType.CANCEL);
        
        VBox clockVBox = new VBox();
        clockVBox.setStyle("-fx-font-size: 16px;");
        
        CheckBox useComputer = new CheckBox("Sync with the local computer");
        VBox manualVBox = new VBox();
        manualVBox.setSpacing(5.0);
        manualVBox.disableProperty().bind(useComputer.selectedProperty());
        
        HBox dateHBox = new HBox();
        dateHBox.setSpacing(5.0);
        Label dateLabel = new Label("Date:");
        dateLabel.setMinWidth(40);
        DatePicker ultraDate = new DatePicker();
        dateHBox.getChildren().addAll(dateLabel,ultraDate);
        
        HBox timeHBox = new HBox();
        timeHBox.setSpacing(5.0);
        Label timeLabel = new Label("Time:");
        timeLabel.setMinWidth(40);
        TextField ultraTime = new TextField();
        timeHBox.getChildren().addAll(timeLabel,ultraTime);
        
        HBox tzHBox = new HBox();
        tzHBox.setSpacing(5.0);
        Label tzLabel = new Label("TimeZone:");
        tzLabel.setMinWidth(40);
        Spinner<Integer> tzSpinner = new Spinner<>(-23, 23, localTZ);    
        tzHBox.getChildren().addAll(tzLabel,tzSpinner);

        manualVBox.getChildren().addAll(dateHBox,timeHBox,tzHBox);
        
        CheckBox autoGPS = new CheckBox("Use GPS to auto-set the clock");
        autoGPS.setSelected(true);

        
        clockVBox.getChildren().addAll(useComputer,manualVBox,autoGPS);
        dialog.getDialogPane().setContent(clockVBox);
        
        BooleanProperty timeOK = new SimpleBooleanProperty(false);

        ultraTime.textProperty().addListener((observable, oldValue, newValue) -> {
            timeOK.setValue(false);
            if (DurationParser.parsable(newValue)) timeOK.setValue(Boolean.TRUE);
            if ( newValue.isEmpty() || newValue.matches("^[0-9]*(:?([0-5]?([0-9]?(:([0-5]?([0-9]?)?)?)?)?)?)?") ){
                System.out.println("Possiblely good start Time (newValue: " + newValue + ")");
            } else {
                Platform.runLater(() -> {
                    int c = ultraTime.getCaretPosition();
                    if (oldValue.length() > newValue.length()) c++;
                    else c--;
                    ultraTime.setText(oldValue);
                    ultraTime.positionCaret(c);
                });
                System.out.println("Bad clock time (newValue: " + newValue + ")");
            }
        });
        
        
        ultraDate.setValue(LocalDate.now());
        ultraTime.setText(LocalTime.ofSecondOfDay(LocalTime.now().toSecondOfDay()).toString());

        Node createButton = dialog.getDialogPane().lookupButton(setButtonType);
        createButton.disableProperty().bind(timeOK.not());
        
        dialog.setResultConverter(dialogButton -> {
            if (dialogButton == setButtonType) {
                return Boolean.TRUE;
            }
            return null;
        });

        Optional<Boolean> result = dialog.showAndWait();

        if (result.isPresent()) {
            if (useComputer.selectedProperty().get()) {
                System.out.println("Timezone check: Local :" + localTZ + " ultra: " + ultraTZ);
                if (localTZ.equals(ultraTZ)) setClock(LocalDateTime.now(),null,autoGPS.selectedProperty().get());
                else setClock(LocalDateTime.now(),localTZ,autoGPS.selectedProperty().get());
            } else {
                LocalTime time = LocalTime.MIDNIGHT.plusSeconds(DurationParser.parse(ultraTime.getText()).getSeconds());
                Integer newTZ = tzSpinner.getValue();
                if (newTZ.equals(ultraTZ)) setClock(LocalDateTime.of(ultraDate.getValue(), time),null,autoGPS.selectedProperty().get());
                else {
                    setClock(LocalDateTime.of(ultraDate.getValue(), time),newTZ,autoGPS.selectedProperty().get());
                }
            }
            
        }
    }
    
    private void clockIssuesCheck(CountDownLatch latch){
        Task ultraCommand = new Task<Void>() {
                @Override public Void call() {
                    if (connectedStatus.get()) {
                        Boolean aquired = false;
                       
                        try {
                            if (latch != null) latch.await();
                            // time check
                            Boolean timeOK=true;
                            Boolean tzOK=true;
                            // TZ check
                            Integer localTZ = TimeZone.getDefault().getOffset(System.currentTimeMillis())/3600000;
                            Integer ultraTZ = Integer.parseInt(ultraSettings.get("23"));
                            System.out.println("Timezone check: Local :" + localTZ + " ultra: " + ultraTZ);
                            String issues = "";
                            if (!localTZ.equals(ultraTZ))  {
                                timeOK=false;
                                tzOK=false; // flip this out so that the setClock below won't adjust the TZ
                                            // adjusting the TZ requires a network reader interface reset. 
                                issues = "Timezone mismatch: Local: " + localTZ + " ultra: " + ultraTZ +"\n";
                            }
                            
                            if (!LocalDate.now().equals(ultraClock.date)) {
                                timeOK=false;
                                issues += "Clock Date Mismatch: Local: "+ LocalDate.now() + " ultra: " + ultraClock.date+"\n";
                            }
                            System.out.println("Date check: Local :" + LocalDate.now() + " ultra: " + ultraClock.date);

                            if (Duration.between(ultraClock.time, ultraClock.takenAt).abs().getSeconds() > 60) {
                                timeOK=false;
                                issues += "Clock Time Mismatch: Local: "+ ultraClock.takenAt + " ultra: " + ultraClock.time+"\n";
                            }
                             System.out.println("Time check: Local :" + ultraClock.takenAt + " ultra: " + ultraClock.time);
                             
                            if (!timeOK) {
                                
                                System.out.println("Time issues!!!");
                                
                                if (readingStatus.get()) {
                                    issues += "\nThese cannot be fixed when the Ultra is in 'Read' mode. \n" +
                                            "Either stop the reader and use the \"Sync Clock\" option "
                                            + "under the \"Advanced Settings\" area to fix the clock " +
                                            " or use the skew option on the reader input to adjust the time. "; 
                                    String timeIssues = issues;
                                    Platform.runLater(() -> {
                                        Alert alert = new Alert(AlertType.WARNING);
                                        alert.setTitle("Ultra Clock Issues");
                                        alert.setHeaderText("Issues detected with the clock...");
                                        alert.setContentText(timeIssues);
                                        alert.showAndWait();
                                    });
                                } else {
                                    issues += "\n Do you want PikaTimer to fix these for you?";
                                    String timeIssues = issues;
                                    Boolean goodTZ = tzOK;
                                    Platform.runLater(() -> {
                                        Dialog<Boolean> dialog = new Dialog();
                                        dialog.setTitle("Ultra Clock Issues");
                                        dialog.setHeaderText("Issues detected with the clock...");
                                        dialog.setContentText(timeIssues);
                                        ButtonType fixButtonType = new ButtonType("Yes", ButtonBar.ButtonData.OK_DONE);
                                        ButtonType cancelButtonType = new ButtonType("No", ButtonBar.ButtonData.CANCEL_CLOSE);
                                        dialog.getDialogPane().getButtonTypes().addAll(fixButtonType, cancelButtonType);

                                        dialog.setResultConverter(dialogButton -> {
                                            if (dialogButton == fixButtonType) {
                                                return Boolean.TRUE;
                                            }
                                            return null;
                                        });
                                        Optional<Boolean> result = dialog.showAndWait();

                                        if (result.isPresent()) {
                                            if (goodTZ) setClock(LocalDateTime.now(),null,true);
                                            else setClock(LocalDateTime.now(),localTZ,true);
                                        }
                                    });
                                }
                            }
                            else System.out.println("Time loogs good");
                            // now let's populate the settings box
                        } catch (Exception ex) {
                            Logger.getLogger(PikaRFIDDirectReader.class.getName()).log(Level.SEVERE, null, ex);
                        } finally {
                            if (aquired) okToSend.release();
                        }
                    }
                    return null;
                }
            };
            new Thread(ultraCommand).start();
    }
    
        public void updateReaderSettings(){
        Task ultraCommand = new Task<Void>() {
                @Override public Void call() {
                    if (connectedStatus.get()) {
                        Boolean aquired = false;
                        try {
                            if (okToSend.tryAcquire(10, TimeUnit.SECONDS)){
                                aquired = true;
                                Boolean commit=false;
                                Boolean restartInterface=false;
                                
                                // Beeper Volume
                                String volume = beeperVolumeChoiceBox.getSelectionModel().getSelectedItem();
                                if (volume != null) {
                                    byte val = 3;
                                    if (volume.equals("Off")) val = 0;
                                    else if (volume.equals("Soft")) val = 1;
                                    else if (volume.equals("Loud")) val = 2;
                                    
                                    System.out.println("updateReaderSettings(): Setting beeper volume (0x21) " + volume + "(" + Byte.toString(val) + ")");

                                    if (val != 3) {
                                        ultraOutput.flush();

                                        ultraOutput.writeBytes("u");
                                        ultraOutput.writeByte(33);  // 0x21, volume
                                        ultraOutput.writeByte(val);
                                        ultraOutput.writeByte(255);

                                        ultraOutput.flush();
                                        String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                        if (result != null) {
                                            ultraSettings.put("21",Byte.toString(val));
                                            commit=true;
                                        } else {
                                        // timeout
                                            System.out.println("Timeout with command 'u0x21'");
                                        }
                                    }
                                } else {
                                   System.out.println("updateReaderSettings(): Beeper volume is NULL!");
                                }
                                // Mode
                                String mode = reader1ModeChoiceBox.getSelectionModel().getSelectedItem();
                                if (! isJoey.get() && mode != null){
                                    System.out.println("updateReaderSettings(): Sending reader mode (0x14/0x15) command");
                                    byte val = 0;
                                    if (mode.equals("Start")) val = 0;
                                    if (mode.equals("Finish")) val = 3;
                                           
                                    ultraOutput.flush();

                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(20);  // 0x14, Reader 1 mode
                                    ultraOutput.writeByte(val);
                                    ultraOutput.writeByte(255);
                                    ultraOutput.flush();
                                    String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    if (result != null) {
                                        if (mode.equals("Start")) ultraSettings.put("14", "0");
                                        else ultraSettings.put("14", "3");
                                        commit=true;
                                        restartInterface=true;
                                    } else {
                                    // timeout
                                        System.out.println("Timeout with command 'u0x20'");
                                    }
                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(21);  // 0x15, Reader 2 mode
                                    ultraOutput.writeByte(val);
                                    ultraOutput.writeByte(255);
                                    ultraOutput.flush();
                                    result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    //result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    if (result != null) {
                                        if (mode.equals("Start")) ultraSettings.put("15", "0");
                                        else ultraSettings.put("15", "3");
                                        commit=true;
                                        restartInterface=true;
                                    } else {
                                    // timeout
                                        System.out.println("Timeout with command 'u0x21'");
                                    }
                                }
                                
                                Integer gf = gatingIntervalSpinner.getValue();
                                if (gf != null && "Finish".equals(mode)){
                                    System.out.println("updateReaderSettings(): Sending gating interval (0x1E) command");
                                    
                                    ultraOutput.flush();

                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(30);  // 0x1e, Gating Interval
                                    ultraOutput.writeBytes(gf.toString());
                                    ultraOutput.writeByte(255);

                                    ultraOutput.flush();
                                    String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    if (result != null) {
                                        ultraSettings.put("30",gf.toString());
                                        commit=true;
                                        restartInterface=true;
                                    } else {
                                    // timeout
                                        System.out.println("Timeout with command 'u0x1E'");
                                    }
                                } else if ("Start".equals(mode)){
                                    ultraSettings.put("30","1");
                                    Platform.runLater(() -> {gatingIntervalSpinner.getValueFactory().setValue(1);});
                                }
                                
                                if (commit){
                                    System.out.println("updateReaderSettings(): Sending commit (u 0xFF 0xFF) command");
                                    // t[0x20]HH:MM:SS DD-MM-YYYY  
                                    ultraOutput.flush();

                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(255);
                                    ultraOutput.writeByte(255);
                                    ultraOutput.flush();
                                    String result = commandResultQueue.poll(10, TimeUnit.SECONDS);
                                    if (result != null) {

                                    } else {
                                    // timeout
                                        System.out.println("Timeout with command 'u0xFF'");
                                    }
                                }
                                if (restartInterface){ // This will result in a disconnect
                                    System.out.println("updateReaderSettings(): Sending reset interface (0x2D) command");
                                    
                                    ultraOutput.flush();

                                    ultraOutput.writeBytes("u");
                                    ultraOutput.writeByte(45);
                                    ultraOutput.writeByte(255);
                                    ultraOutput.flush();
                                    
                                }
                            } else {
                                // timeout
                                System.out.println("Timeout waiting to update the reader settings");
                            }
                        } catch (Exception ex) {
                            Logger.getLogger(PikaRFIDDirectReader.class.getName()).log(Level.SEVERE, null, ex);

                        } finally {
                            if (aquired) System.out.println("Relasing transmit lock");
                            if (aquired) okToSend.release();
                        }
                    }
                    
                    
                    return null;
                }
        };
        new Thread(ultraCommand).start();
        updateSettingsButton.visibleProperty().set(false);
    }

    private static class Ultra {

        @Override
        public int hashCode() {
            int hash = 7 + IP.hashCode() + MAC.hashCode();
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final Ultra other = (Ultra) obj;
            if (!this.IP.getValueSafe().equals(other.IP.getValueSafe()) ) {
                return false;
            }
            if (!this.MAC.getValueSafe().equals(other.MAC.getValueSafe()) ) {
                return false;
            }
            return true;
        }
        public StringProperty IP  = new SimpleStringProperty();
        public StringProperty MAC  = new SimpleStringProperty();
        public StringProperty TYPE = new SimpleStringProperty();
        public Ultra() {
            
        }
        public Ultra(String host){
            IP.set(host);
        }
        public StringProperty ipProperty() {
            return IP;
        }
        public StringProperty macProperty(){
            return MAC;
        }
        public StringProperty typeProperty(){
            return TYPE;
        }
        public String toString(){
            return IP.getValueSafe() + " " + TYPE.getValueSafe() + " (" + MAC.getValueSafe() + ")";
        }
    }

    private static class RewindData {
        public LocalDate startDate;
        public LocalDate endDate;
        public Duration startTime;
        public Duration endTime;

        public RewindData() {
        }
    }

    private static class UltraClock {
        public LocalDate date;
        public LocalTime time;
        public Duration tzOffset;
        public Boolean tzHalfOffsetSupport;
        public LocalTime takenAt;
        public UltraClock() {
        }
    }
}