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

package com.abc.usbdemo;

import static com.abc.usbdemo.UsbTerminalHelper.getFirmwareVersion;
import static com.abc.usbdemo.Util.bytesToHexString;
import static com.abc.usbdemo.Util.stringToBytes;

import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
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.AppCompatActivity;

import com.abc.terminalfactory.UsbSmartCard;

import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

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 {
    private static final String TAG = "USB_MAIN_ACTIVITY";

    private CardTerminal cardTerminal;
    private Card card;
    private CardChannel channel;

    /**
     * ListView
     */
    private ListView listViewLog;

    /**
     * ArrayAdapter
     */
    private ArrayAdapter<String> listAdapterLog;
    Thread UpdateTerminalSelectorThread;
    Thread CardStateMonitorThread = null;
    /**
     * Buttons
     */
    private Button btnRefreshTerminal;
    private Button btnConnect;
    private Button btnApduExchange;
    /**
     * CheckBox
     */
    private CheckBox checkBoxT0, checkBoxT1;

    private RadioButton radioButtonModeDirect, radioButtonModeExclusive;

    /**
     * EditText
     */
    private EditText editTextExchange, editTextControl;

    /**
     * Spinner
     */
    private Spinner spinnerTerminalSelector;

    /**
     * SpinnerAdapter
     */
    private TerminalAdapter mTerminalAdapter;
    private Button btnEscapeCommand;

    /**
     * onCreate runs when this activity is created, It starts the USB terminal manager and obtains a reference to the ABC Terminal Factory
     *
     * @param savedInstanceState saved InstanceState
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        InitializeUI();

        if (getSupportActionBar() != null) {
            getSupportActionBar().setDisplayShowHomeEnabled(true);
            getSupportActionBar().setIcon(R.mipmap.logo_04);
        }

        // Use this check to determine whether USB Host is supported on this device
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {

            Toast.makeText(this, R.string.usb_not_supported, Toast.LENGTH_SHORT).show();
            finish();
            return;
        }


        // Get the USB terminal manager
        if (UsbSmartCard.getInstance(this).getManager() == null) {

            Toast.makeText(this, R.string.usb_not_supported, Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        refreshTerminalSelector();

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

    @Override
    protected void onStop() {
        try {
            if (card != null) {
                card.disconnect(true);
            }
        } catch (CardException e) {
            log(e.getMessage());
        }

        log("onStop()");
        UsbSmartCard.getInstance(this).onStop(); // Deregister USB Intents

        // Stop auto refresh task
        StopTerminalAutoRefresh();
        super.onStop();
    }

    /**
     * Dispatch onPause() to fragments.
     */
    @Override
    protected void onPause() {
        log("Pause");
        super.onPause();
    }

    /**
     * Dispatch onResume() to fragments.  Note that for better inter-operation
     * with older versions of the platform, at the point of this call the
     * fragments attached to the activity are <em>not</em> resumed.
     */
    @Override
    public void onResume() {
        // Start an async task to update Terminal Selector when new terminals are updated
        UsbSmartCard.getInstance(this).onResume(); // Re-register USB Intents
        StartTerminalAutoRefresh();
        super.onResume();
        log("onResume()");
    }

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

    /**
     * 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.message_detail);
        listViewLog.setAdapter(listAdapterLog);
        listViewLog.setDivider(null);


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


        // List
        btnRefreshTerminal.setOnClickListener(view ->
        {
            refreshTerminalSelector();
            log("Refresh");
        });

        // 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());
                }
            }
        });

        final boolean[] card_monitoring = {false};

        // Card Monitor 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(view ->
        {
            if (btnConnect.getText().toString().equals(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";
                }
                log("Connect protocol: " + protocol);

                try {
                    Connect(cardTerminal, protocol);
                    if (protocol.equals("direct")) {
                        setUiState(UI_STATE.DIRECT);
                    } else {
                        setUiState(UI_STATE.CONNECTED);
                    }
                } catch (Exception e) {
                    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(getString(R.string.disconnect))) {
                try {
                    Disconnect();
                    setUiState(UI_STATE.READY_TO_CONNECT);
                } catch (Exception e) {
                    log(e.getMessage());
                }
            }
        });

        // APDU ApduExchange
        btnApduExchange.setOnClickListener(v ->
        {
            try {
                ExchangeAPDU(editTextExchange.getText().toString(), channel);
            } catch (CardException e) {
                try {
                    Disconnect();
                } catch (CardException cardException) {
                    MainActivity.this.log(cardException.getMessage());
                }
                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) {
                    MainActivity.this.log(cardException.getMessage());
                }
                MainActivity.this.setUiState(UI_STATE.READY_TO_CONNECT);
                MainActivity.this.log(e.getMessage());
            }
        });

        // Clear
        btnClearLog.setOnClickListener(view -> 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.toString());
                setUiState(UI_STATE.READY_TO_CONNECT);
            }

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

                setUiState(UI_STATE.DISCONNECTED);
            }
        });

        setUiState(UI_STATE.DISCONNECTED);
    }

    /**
     * This method enables and disables UI components depending on terminal and card connection status
     *
     * @param mUiState (Disconnected, Ready to connect and Connected)
     */
    private void setUiState(UI_STATE mUiState) {
        runOnUiThread(() -> {
            switch (mUiState) {
                case DISCONNECTED:
                    btnRefreshTerminal.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);
                    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);
                    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);
                    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;
            }
        });
    }

    /**
     * This method tries to connect to to a card in the terminal and gets a channel for APDU and Escape command communication.
     *
     * @param mTerminal Card Terminal
     * @param protocol  T=0, T=1, *, direct
     * @throws CardException exception throw on card error e.g. connect() failed
     */
    private void Connect(CardTerminal mTerminal, String protocol) throws CardException {
        card = mTerminal.connect(protocol);
        channel = card.getBasicChannel();
        log("Card ATR: " + bytesToHexString(card.getATR().getBytes()));
        log(card.toString());
    }

    /**
     * This method disconnects card
     *
     * @throws CardException cardException
     */
    private void Disconnect() throws CardException {
        card.disconnect(true);
    }

    /**
     * This method gets a list of card terminal from the terminal factory and populates drop down 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", UsbSmartCard.getInstance(this).getManager(), new UsbTerminalProvider());
                // } catch (Exception e)
                // {
                //     e.printStackTrace();
                // }

                //////////////////
                // Get Terminals
                //////////////////
                CardTerminals cardTerminals;
                ////
                // 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)
                ////
                cardTerminals = UsbSmartCard.terminals();

                List<CardTerminal> cardTerminalsList = cardTerminals.list();
                mTerminalAdapter.updateTerminals(cardTerminalsList);
                if (!cardTerminalsList.isEmpty()) {
                    cardTerminal = mTerminalAdapter.getTerminal(0);
                    log("Terminal Selected: " + cardTerminal.toString());
                    setUiState(UI_STATE.READY_TO_CONNECT);
                }

                // Example code to get SCR slot Status
//                if (cardTerminal != null) {
//                    log("Card: " + (cardTerminal.isCardPresent() ? "(0 ACTIVE / 1 INACTIVE)" : "2 ABSENT"));
//                }
            }
        } catch (CardException e) {
            log(e.toString());
        }
    }

    /**
     * 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));
    }

    /**
     * It takes a string as an argument, and then logs it to the Android log, and also writes it to the
     * log window
     *
     * @param text The text to be displayed in the log window.
     */
    private void log(String text) {
        Log.v(TAG, text);
        writeLogWindow(text);
    }

    /**
     * This Method Writes a string to the log window on the android GUI
     *
     * @param text string to print
     */
    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) // OPTIONAL, set to false to disable auto refresh
        {
            UpdateTerminalSelectorThread = new Thread(() ->
            {
                List<CardTerminal> cardTerminalsListOld = new ArrayList<>();
                while (!Thread.currentThread().isInterrupted()) {
                    try {
                        List<CardTerminal> cardTerminalsList = UsbSmartCard.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 {
                        UsbSmartCard.terminals().waitForChange(1);
                        List<CardTerminal> cardTerminalsList;
                        cardTerminalsList = UsbSmartCard.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()));
                            card.disconnect(true);
                        }
                        cardTerminalsList = UsbSmartCard.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;
    }

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