/*
 * Copyright (c) 2019 - 2026 AB Circle Limited. All rights reserved
 */

package com.abc.bluetoothdemo;

import static com.abc.bluetoothdemo.BluetoothTerminalHelper.disconnectTerminal;
import static com.abc.bluetoothdemo.BluetoothTerminalHelper.getBatteryLevel;
import static com.abc.bluetoothdemo.BluetoothTerminalHelper.getBatteryMillivolts;
import static com.abc.bluetoothdemo.BluetoothTerminalHelper.getFirmwareVersion;
import static com.abc.bluetoothdemo.BluetoothTerminalHelper.getSerialNumber;
import static com.abc.bluetoothdemo.BluetoothTerminalHelper.isACKProtocolEnabled;
import static com.abc.bluetoothdemo.Util.bytesToHexString;
import static com.abc.bluetoothdemo.Util.stringToBytes;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.RadioButton;
import android.widget.Spinner;
import android.widget.Toast;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.preference.PreferenceManager;

import com.abc.bluetoothTerminalfactory.AbCircleBluetoothTerminalManager;
import com.abc.bluetoothTerminalfactory.BluetoothSmartCard;
import com.abc.bluetoothTerminalfactory.DiscoveredReader;
import com.abc.bluetoothTerminalfactory.ReaderDiscoveredCallback;

import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.smartcardio.Card;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CardException;
import javax.smartcardio.CardTerminal;
import javax.smartcardio.CardTerminals;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;

public class MainActivity extends AppCompatActivity implements ReaderDiscoveredCallback {
    // Preference
    private SharedPreferences prefs;

    private AbCircleBluetoothTerminalManager mManager;
    private CardTerminal cardTerminal;
    private Card card;
    private CardChannel channel;
    private Thread updateTerminalSelectorThread;
    private Thread cardStateMonitorThread;


    // ListView
    private ListView listViewLog;

    // ArrayAdapter
    private ArrayAdapter<String> listAdapterLog;

    // Buttons
    private Button btnRefreshTerminal;
    private Button btnDiscoverTerminal;
    private Button btnConnect;
    private Button btnApduExchange;
    private Button btnEscapeCommand;

    // CheckBox
    private CheckBox checkBoxT0, checkBoxT1;

    private RadioButton radioButtonModeDirect, radioButtonModeExclusive;


    // EditText
    private EditText editTextExchange;
    private EditText editTextControl;


    // Spinner
    private Spinner spinnerTerminalSelector;

    // SpinnerAdapter
    private TerminalAdapter mTerminalAdapter;

    private ExecutorService executorService;

    /**
     * > This function is called when the user clicks the menu button
     *
     * @param menu The menu object that you want to inflate.
     * @return True
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.app_menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int itemId = item.getItemId();
        if (itemId == R.id.menu_settings) {
            startActivity(new Intent(this, PreferenceActivity.class));
        } else if (itemId == R.id.menu_help) {
            startActivity(new Intent(this, AboutActivity.class));
        }
        return super.onOptionsItemSelected(item);
    }

    public void statusCheck() {
        final LocationManager manager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

        if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
            final AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setMessage("Location access is needed for BLE, open location settings?")
                    .setCancelable(false)
                    .setPositiveButton("Yes", (dialog, id) -> startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)))
                    .setNegativeButton("No", (dialog, id) -> dialog.cancel());
            final AlertDialog alert = builder.create();
            alert.show();
        }
    }

    public static boolean hasPermissions(Context context, String... permissions) {
        if (context != null && permissions != null) {
            for (String permission : permissions) {
                if (ActivityCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
                    return false;
                }
            }
        }
        return true;
    }

    private static final int PERMISSION_ALL = 1;

    private static final String[] PERMISSIONS_OLD = {
            android.Manifest.permission.ACCESS_COARSE_LOCATION,
            android.Manifest.permission.ACCESS_FINE_LOCATION,
    };

    private static final String[] PERMISSIONS = {
            android.Manifest.permission.BLUETOOTH_SCAN,
            android.Manifest.permission.BLUETOOTH_CONNECT,
    };

    private void enableBluetooth() {
        BluetoothAdapter mBtAdapter = BluetoothAdapter.getDefaultAdapter();

        if (mBtAdapter == null) {
            Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
            finish();
            return;
        }

        if (!mBtAdapter.isEnabled()) {
            final AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setMessage("Bluetooth needs to be enabled, open bluetooth settings?")
                    .setCancelable(false)
                    .setPositiveButton("Yes", (dialog, id) -> startActivity(new Intent(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS)))
                    .setNegativeButton("No", (dialog, id) -> dialog.cancel());
            final AlertDialog alert = builder.create();
            alert.show();
        }

        /**
         Ensures Bluetooth is enabled on the device.  If Bluetooth is not currently enabled,
         fire an intent to display a dialog asking the user to grant permission to enable it.
         */
        String[] permissions = (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.S) ? PERMISSIONS_OLD : PERMISSIONS;
        if (!hasPermissions(this, permissions)) {
            ActivityCompat.requestPermissions(this, permissions, PERMISSION_ALL);
        }
    }


    /**
     * The above function is the onCreate function. It is called when the application is first created.
     *
     * @param savedInstanceState If the activity is being re-initialized after previously being shut
     *                           down then this Bundle contains the data it most recently supplied in
     *                           onSaveInstanceState(Bundle). Note: Otherwise it is null.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Preference
        prefs = PreferenceManager.getDefaultSharedPreferences(this);

        // AB Circle Logo
        Objects.requireNonNull(getSupportActionBar()).setDisplayShowHomeEnabled(true);
        getSupportActionBar().setIcon(R.mipmap.logo_04);

        // Assign UI Handles
        InitializeUI();

        // Enable bluetooth and get location permission for bluetooth low energy
        enableBluetooth();

        // Get the Bluetooth terminal manager
        mManager = BluetoothSmartCard.getInstance(this).getManager();
        if (mManager == null) {
            Toast.makeText(this, R.string.bluetooth_not_supported, Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        log("Application Start");
        log("Using:");
        log(BluetoothSmartCard.getVersion());
        log(BluetoothSmartCard.getCopyright());

        executorService = Executors.newSingleThreadExecutor();
    }

    @Override
    public void onStart() {
        super.onStart();
        log("onStart()");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        log("onRestart()");
    }

    @Override
    public void onResume() {
        enableBluetooth();
        statusCheck();
        BluetoothSmartCard.getInstance(this).onResume(); // <- must Add

        MainActivity.this.refreshTerminalSelector();
        StartTerminalAutoRefresh();

        super.onResume();
        log("onResume()");
    }


    @Override
    protected void onPause() {
        log("onPause()");
        super.onPause();
    }

    @Override
    protected void onStop() {
        log("onStop()");
        BluetoothSmartCard.getInstance(this).onStop();

        // Stop auto refresh task
        StopTerminalAutoRefresh();

        super.onStop();
    }

    @Override
    public void onDestroy() {
        log("onDestroy()");
        super.onDestroy();

        if (executorService != null) {
            executorService.shutdown();
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == PERMISSION_ALL) {
            if (resultCode == Activity.RESULT_OK) {
                log("Additional bluetooth permission requested");
            }
        }
    }

    /**
     * Connect to reader with custom encryption key, custom encryption key can be changed via USB
     */
    private void SetEncryptionKey() {
        try {
            // Set Encryption key based on Settings page
            if (prefs.getString("encryption_key", "Default").equals("Custom")) {
                byte[] encryptionKey = stringToBytes(prefs.getString("custom_encryption_key", ""));
                log("Custom encryption Key: " + bytesToHexString(encryptionKey));
                mManager.SetEncryptionKey(encryptionKey);
            } else {
                mManager.SetEncryptionKey(null);
            }
        } catch (Exception e) {
            log(e.getMessage());
        }
    }

    /**
     * Enable or disable ACK protocol for newly connected readers (fw 1.09 or above only)
     */
    private void SetACKProtocol() {
        try {
            // Set ACK protocol based on Settings page
            boolean ackEnabled = prefs.getString("ack_protocol", "Disabled").equals("Enabled");

            mManager.setACKProtocolEnabledForReaders(ackEnabled);
        } catch (Exception e) {
            log(e.getMessage());
            mManager.setACKProtocolEnabledForReaders(false);
        }
    }

    /**
     * This method enables and disables UI components depending on terminal and card connection status
     *
     * @param mUiState The current state of the UI.
     */
    private void setUiState(UI_STATE mUiState) {
        runOnUiThread(() -> {
            switch (mUiState) {
                case DISCONNECTED:
                    btnRefreshTerminal.setEnabled(true);
                    btnDiscoverTerminal.setEnabled(true);
                    btnConnect.setEnabled(false);
                    btnConnect.setText(R.string.connect);
                    editTextExchange.setEnabled(false);
                    editTextControl.setEnabled(false);
                    btnApduExchange.setEnabled(false);
                    btnEscapeCommand.setEnabled(false);
                    spinnerTerminalSelector.setEnabled(true);
                    radioButtonModeDirect.setEnabled(true);
                    radioButtonModeExclusive.setEnabled(true);
                    checkBoxT0.setEnabled(true);
                    checkBoxT1.setEnabled(true);
                    break;

                case READY_TO_CONNECT:
                    btnRefreshTerminal.setEnabled(true);
                    btnDiscoverTerminal.setEnabled(true);
                    btnConnect.setEnabled(true);
                    btnConnect.setText(R.string.connect);
                    editTextExchange.setEnabled(false);
                    editTextControl.setEnabled(false);
                    btnApduExchange.setEnabled(false);
                    btnEscapeCommand.setEnabled(false);
                    spinnerTerminalSelector.setEnabled(true);
                    radioButtonModeDirect.setEnabled(true);
                    radioButtonModeExclusive.setEnabled(true);
                    checkBoxT0.setEnabled(true);
                    checkBoxT1.setEnabled(true);
                    break;

                case DIRECT:
                    btnRefreshTerminal.setEnabled(false);
                    btnDiscoverTerminal.setEnabled(false);
                    btnConnect.setEnabled(true);
                    btnConnect.setText(R.string.disconnect);
                    editTextExchange.setEnabled(false);
                    editTextControl.setEnabled(true);
                    btnApduExchange.setEnabled(false);
                    btnEscapeCommand.setEnabled(true);
                    spinnerTerminalSelector.setEnabled(false);
                    radioButtonModeDirect.setEnabled(false);
                    radioButtonModeExclusive.setEnabled(false);
                    checkBoxT0.setEnabled(false);
                    checkBoxT1.setEnabled(false);
                    break;

                case CONNECTED:
                    btnRefreshTerminal.setEnabled(false);
                    btnDiscoverTerminal.setEnabled(false);
                    btnConnect.setEnabled(true);
                    btnConnect.setText(R.string.disconnect);
                    editTextExchange.setEnabled(true);
                    editTextControl.setEnabled(true);
                    btnApduExchange.setEnabled(true);
                    btnEscapeCommand.setEnabled(true);
                    spinnerTerminalSelector.setEnabled(false);
                    radioButtonModeDirect.setEnabled(false);
                    radioButtonModeExclusive.setEnabled(false);
                    checkBoxT0.setEnabled(false);
                    checkBoxT1.setEnabled(false);
                    break;

                default:
                    break;
            }
        });
    }

    /**
     * Connect to the card using the specified protocol
     *
     * @param mTerminal The card terminal to connect to.
     * @param protocol  T=0, T=1, T=CL (contactless)
     */
    private void Connect(CardTerminal mTerminal, String protocol) throws CardException {
        if (cardTerminal != null) {
            card = mTerminal.connect(protocol);
            channel = card.getBasicChannel();
            log("Card ATR: " + bytesToHexString(card.getATR().getBytes()));
            log(card.toString());
        }
    }

    /**
     * This function disconnects the card from the reader
     */
    private void Disconnect() throws CardException {
        card.disconnect(true);
    }

    /**
     * Refresh Terminal List
     */
    private void refreshTerminalSelector() {
        try {
            if (mTerminalAdapter != null) {
                ////////////////
                // Get Factory
                ////////////////
//                TerminalFactory terminalFactory = null;
//                try
//                {
//                    // Get terminal Factory of type AbcUsb from USBTerminalProvider passing the terminal manager as parameter
//                    terminalFactory = TerminalFactory.getInstance("AbcUsb", BluetoothSmartCard.getInstance(this).getManager(), new AbCircleBluetoothTerminalFactoryProvider());
//                } catch (Exception e)
//                {
//                    e.printStackTrace();
//                }

                //////////////////
                // Get Terminals
                //////////////////
                CardTerminals cardTerminals = null;
                ////
                // Method 1 - Through terminalFactory
                ////
//                cardTerminals = terminalFactory.terminals();
                ////
                // Method 2 - Directly - better compatibility with different versions of java (java.security.provider is different between versions of JAVA)
                ////
                BluetoothSmartCard.getInstance(this);
                cardTerminals = BluetoothSmartCard.terminals();

                List<CardTerminal> cardTerminalsList = cardTerminals.list();
                mTerminalAdapter.updateTerminals(cardTerminalsList);
                if (!cardTerminalsList.isEmpty()) {
                    cardTerminal = mTerminalAdapter.getTerminal(0);
                    try {
                        log("Terminal Selected " + cardTerminal.getName());
                        log("- Serialnumber: " + getSerialNumber(cardTerminal));
                        log("- ACK protocol enabled: " + (isACKProtocolEnabled(cardTerminal) ? "yes" : "no"));
                        setUiState(UI_STATE.READY_TO_CONNECT);
                    } catch (Exception | IllegalAccessError e) {
                        log(e.getMessage());
                    }
                }
            }
        } catch (CardException e) {
            log(e.toString());
        }
    }

    @Override
    public void onReaderDiscovered(DiscoveredReader reader) {
        log(reader.getName() + " : " + reader.getMac());
    }

    private enum UI_STATE {
        DISCONNECTED,
        READY_TO_CONNECT,
        DIRECT,
        CONNECTED,
    }

    /**
     * It takes a string of hexadecimal characters, converts it to a byte array, wraps it in a
     * CommandAPDU object, sends it to the card, and then prints the response
     *
     * @param commandString The command to send to the card.
     * @param channel       The channel to use for communication with the card.
     */
    private void ExchangeAPDU(String commandString, CardChannel channel) throws CardException {
        CommandAPDU commandAPDU = new CommandAPDU(stringToBytes(commandString));
        log("CommandAPDU: " + bytesToHexString(commandAPDU.getBytes()));

        ResponseAPDU responseAPDU = channel.transmit(commandAPDU);
        log("ResponseAPDU: " + bytesToHexString(responseAPDU.getBytes()));
    }

    /**
     * It sends an escape command to the reader and logs the response
     *
     * @param commandString The command string to send to the card.
     * @param card          The card object that you get from the card reader.
     */
    private void SendEscapeCommand(String commandString, Card card) throws CardException {
        byte[] escapeCommand = stringToBytes(commandString);
        log("EscapeCommand: " + bytesToHexString(escapeCommand));

        byte[] escapeResponse = card.transmitControlCommand(3500, escapeCommand);
        log("EscapeResponse: " + bytesToHexString(escapeResponse));
    }

    /**
     * This method assigns event handlers to UI objects (buttons, text boxes etc)
     */
    private void InitializeUI() {


        // Setup Log List View
        listViewLog = findViewById(R.id.listMessage);
        listAdapterLog = new ArrayAdapter<>(this, R.layout.log_entry);
        listViewLog.setAdapter(listAdapterLog);
        listViewLog.setDivider(null);

        // Setup Buttons
        btnRefreshTerminal = findViewById(R.id.activity_main_button_refresh_terminal);
        btnDiscoverTerminal = findViewById((R.id.activity_main_button_discover_terminal));
        Button btnGetFirmwareVersion = findViewById(R.id.activity_main_button_version);
        Button btnGetBatteryLevel = findViewById(R.id.activity_main_button_get_battery_level);
        Button btnMonitorCard = findViewById(R.id.activity_main_button_Monitor_Card);
        btnConnect = findViewById(R.id.activity_main_button_connect);
        Button btnDisconnect = findViewById(R.id.activity_main_button_disconnect);
        btnApduExchange = findViewById(R.id.activity_main_button_transmit);
        btnEscapeCommand = findViewById(R.id.activity_main_button_control);
        Button btnClearLog = findViewById(R.id.activity_clear);

        // Refresh Terminal List
        btnRefreshTerminal.setOnClickListener(v ->
        {
            StartTerminalAutoRefresh();

            // Bluetooth Scan duration
            int bluetoothScanDuration = 10000;

            // Set Encryption key before connecting to terminals
            SetEncryptionKey();

            // Set ACKProtocol enabled or disabled before connecting to terminals
            SetACKProtocol();

            // Scan for Bluetooth Terminals
            mManager.SetBluetoothRssiFilter(0);
            String filterName = prefs.getString("name_filter", "");
            try {
                mManager.ScanBluetooth(bluetoothScanDuration, filterName);
                MainActivity.this.log(String.format("Scanning Bluetooth for %d seconds...", bluetoothScanDuration / 1000));
            } catch (Exception e){
                writeLogWindow(e.getMessage());
            }
        });

        // Discover Terminal List
        btnDiscoverTerminal.setOnClickListener(v -> {
            MainActivity.this.StartTerminalAutoRefresh();

            // Bluetooth Scan duration
            int bluetoothScanDuration = 10000;

            // Set Encryption key before connecting to terminals
            MainActivity.this.SetEncryptionKey();

            // Set ACKProtocol enabled or disabled before connecting to terminals
            MainActivity.this.SetACKProtocol();

            // Scan for Bluetooth Terminals
            mManager.SetBluetoothRssiFilter(0);
            try {
                mManager.ScanBluetooth(0); // Stop Bluetooth Scanning
                mManager.StartReaderDiscovery(bluetoothScanDuration, this);
                MainActivity.this.log(String.format("Scanning Bluetooth for %d seconds...", bluetoothScanDuration / 1000));
            } catch (Exception e){
                writeLogWindow(e.getMessage());
            }
        });

        // Get Version Button
        btnGetFirmwareVersion.setOnClickListener(v ->
        {
            if (cardTerminal != null) {
                try {
                    // Firmware
                    String firmwareVersion = getFirmwareVersion(cardTerminal);
                    writeLogWindow("Firmware: " + firmwareVersion);
                } catch (Exception e) {
                    writeLogWindow(e.getMessage());
                } catch (IllegalAccessError e) {
                    writeLogWindow(e.getMessage());
                }
            }
        });

        // BatteryLevel Button
        btnGetBatteryLevel.setOnClickListener(v ->
        {
            if (cardTerminal != null) {
                if (cardTerminal != null) {
                    try {
                        // Battery
                        int batteryLevel = getBatteryLevel(cardTerminal);
                        if (batteryLevel == 0xFF) {
                            writeLogWindow("Battery: Charging");
                        } else {
                            writeLogWindow(String.format(Locale.getDefault(), "Battery: %03d%%", batteryLevel));
                        }
                    } catch (Exception e) {
                        writeLogWindow(e.getMessage());
                    } catch (IllegalAccessError e) {
                        writeLogWindow(e.getMessage());
                    }
                }
            }
        });

        final boolean[] card_monitoring = {false};

        // Card Monitoring Button
        btnMonitorCard.setOnClickListener(v ->
        {
            if (!card_monitoring[0]) {
                writeLogWindow("Card Monitoring ON");
                StartCardStateMonitor();
            } else {
                writeLogWindow("Card Monitoring OFF");
                StopCardStateMonitor();
            }
            card_monitoring[0] = !card_monitoring[0];
        });

        // Connect
        btnConnect.setOnClickListener(v ->
        {
            if (btnConnect.getText().toString().equals(MainActivity.this.getString(R.string.connect))) {
                String protocol = "";
                if (radioButtonModeExclusive.isChecked()) {
                    if (checkBoxT0.isChecked() && checkBoxT1.isChecked()) {
                        protocol = "*";
                    } else if (checkBoxT0.isChecked()) {
                        protocol = "T=0";
                    } else if (checkBoxT1.isChecked()) {
                        protocol = "T=1";
                    }
                } else {
                    protocol = "direct";
                }
                MainActivity.this.log("Connect protocol: " + protocol);

                try {
                    MainActivity.this.Connect(cardTerminal, protocol);
                    if (protocol.equals("direct")) {
                        setUiState(UI_STATE.DIRECT);
                    } else {
                        setUiState(UI_STATE.CONNECTED);
                    }
                } catch (Exception e) {
                    MainActivity.this.setUiState(UI_STATE.READY_TO_CONNECT);
                    if (e.getCause() != null) {
                        log(e.getMessage() + ": " + e.getCause().getMessage());
                    } else {
                        log(e.getMessage());
                    }
                }
            } else if (btnConnect.getText().toString().equals(MainActivity.this.getString(R.string.disconnect))) {
                try {
                    MainActivity.this.Disconnect();
                    MainActivity.this.setUiState(UI_STATE.READY_TO_CONNECT);
                } catch (CardException e) {
                    MainActivity.this.log(e.getMessage());
                }
            }
        });

        // Disconnect Terminal
        btnDisconnect.setOnClickListener(v ->
        {
            if (cardTerminal != null) {
                writeLogWindow("Disconnecting " + cardTerminal.getName());
                disconnectTerminal(cardTerminal);
            }
        });

        // APDU ApduExchange
        btnApduExchange.setOnClickListener(v ->
        {
            executorService.execute(() -> {
                try {
                    ExchangeAPDU(editTextExchange.getText().toString(), channel);
                } catch (Exception e) {
                    try {
                        Disconnect();
                    } catch (CardException cardException) {
                        cardException.printStackTrace();
                    }
                    MainActivity.this.setUiState(UI_STATE.READY_TO_CONNECT);
                    MainActivity.this.log(e.getMessage());
                }
            });
        });

        // Escape Command
        btnEscapeCommand.setOnClickListener(v ->
        {
            try {
                SendEscapeCommand(editTextControl.getText().toString(), card);
            } catch (CardException e) {
                try {
                    Disconnect();
                } catch (CardException cardException) {
                    cardException.printStackTrace();
                }
                MainActivity.this.setUiState(UI_STATE.READY_TO_CONNECT);
                MainActivity.this.log(e.getMessage());
            }
        });

        // Clear
        btnClearLog.setOnClickListener(v -> listAdapterLog.clear());

        // Setup Radio Buttons
        radioButtonModeDirect = findViewById(R.id.activity_main_radio_direct);
        radioButtonModeExclusive = findViewById(R.id.activity_main_radio_exclusive);

        // Setup CheckBox
        checkBoxT0 = findViewById(R.id.activity_main_checkbox_t0);
        checkBoxT1 = findViewById(R.id.activity_main_checkbox_t1);

        // Setup EditText
        editTextExchange = findViewById(R.id.activity_main_editText_transmit);
        editTextControl = findViewById(R.id.activity_main_editText_control);

        // SpinnerAdapter
        mTerminalAdapter = new TerminalAdapter(this, android.R.layout.simple_spinner_item);
        mTerminalAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

        // Setup Spinner
        spinnerTerminalSelector = findViewById(R.id.activity_main_spinner_terminal);
        spinnerTerminalSelector.setAdapter(mTerminalAdapter);
        spinnerTerminalSelector.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                cardTerminal = mTerminalAdapter.getTerminal(position);
                log("Terminal Selected " + cardTerminal.getName());
                setUiState(UI_STATE.READY_TO_CONNECT);
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
                log("No Terminal Selected");

                setUiState(UI_STATE.DISCONNECTED);
            }
        });

        setUiState(UI_STATE.DISCONNECTED);
    }

    /**
     * Logs the text to the log window and to the Android log.
     *
     * @param text The text to be written to the log window.
     */
    void log(String text) {
        String TAG = "BLUETOOTH_MAIN_ACTIVITY";
        Log.v(TAG, text);
        writeLogWindow(text);
    }

    /**
     * It adds a new line to the log window.
     *
     * @param text The text to be written to the log window
     */
    private void writeLogWindow(String text) {
        runOnUiThread(() -> {
            //Update the log with time stamp
            String currentDateTimeString = DateFormat.getTimeInstance().format(new Date());
            listAdapterLog.add("[" + currentDateTimeString + "] " + text);
            listViewLog.smoothScrollToPosition(listAdapterLog.getCount());
        });
    }

    private void StartTerminalAutoRefresh() {
        if (true) {
            if (updateTerminalSelectorThread == null) {
                updateTerminalSelectorThread = new Thread(() -> {
                    List<CardTerminal> cardTerminalsListOld = new ArrayList<>();
                    List<CardTerminal> cardTerminalsList;
                    CardTerminals cardTerminals = BluetoothSmartCard.terminals();

                    while (!Thread.currentThread().isInterrupted()) {
                        try {
                            cardTerminalsList = cardTerminals.list();
                            if (!cardTerminalsListOld.equals(cardTerminalsList)) {
                                cardTerminalsListOld = cardTerminalsList;
                                runOnUiThread(MainActivity.this::refreshTerminalSelector);
                            }
                            // Don't over Tax UI
                            Thread.sleep(100);
                        } catch (CardException e) {
                            writeLogWindow(e.getMessage());
                        } catch (InterruptedException e) {
                            break;
                        }
                    }
                });
                updateTerminalSelectorThread.start();
            }
        }
        /*
        if (true) // OPTIONAL, set to false to disable auto refresh
        {
            UpdateTerminalSelectorThread = new Thread(() ->
            {
                List<CardTerminal> cardTerminalsListOld = new ArrayList<>();
                while (!Thread.currentThread().isInterrupted()) {
                    try {
                        List<CardTerminal> cardTerminalsList = BluetoothSmartCard.terminals().list();
                        if (!cardTerminalsListOld.equals(cardTerminalsList)) {
                            cardTerminalsListOld = cardTerminalsList;
                            runOnUiThread(MainActivity.this::refreshTerminalSelector);
                        }
                        // Don't over Tax UI
                        Thread.sleep(100);
                    } catch (CardException e) {
                        writeLogWindow(e.getMessage());
                    } catch (InterruptedException e) {
                        break;
                    }
                }
            });
            UpdateTerminalSelectorThread.start();
        }
        */
    }

    private void StopTerminalAutoRefresh() {
        if (updateTerminalSelectorThread.isAlive()) {
            updateTerminalSelectorThread.interrupt();
            updateTerminalSelectorThread = null;
        }
    }

    private void StartCardStateMonitor() {
        if (cardStateMonitorThread == null) {
            cardStateMonitorThread = new Thread(() ->
            {
                while (!Thread.currentThread().isInterrupted()) {
                    try {
                        BluetoothSmartCard.terminals().waitForChange(1);
                        List<CardTerminal> cardTerminalsList;
                        cardTerminalsList = BluetoothSmartCard.terminals().list(CardTerminals.State.CARD_INSERTION);
                        for (CardTerminal terminal : cardTerminalsList) {
                            writeLogWindow("Card Inserted " + terminal.getName());
                            /** Connect Card */
                            card = terminal.connect("*");
                            CardChannel cardChannel = card.getBasicChannel();
                            writeLogWindow("ATR: " + bytesToHexString(card.getATR().getBytes()));
                        }
                        cardTerminalsList = BluetoothSmartCard.terminals().list(CardTerminals.State.CARD_REMOVAL);
                        for (CardTerminal terminal : cardTerminalsList) {
                            writeLogWindow("Card Removed " + terminal.getName());
                        }
                    } catch (Exception e) {
                        writeLogWindow(e.getMessage());
                    }
                }
            });
            cardStateMonitorThread.start();
        }
    }

    private void StopCardStateMonitor() {
        if (cardStateMonitorThread.isAlive()) {
            cardStateMonitorThread.interrupt();
        }
        cardStateMonitorThread = null;
    }
}