package demo.mapi.ccv.eu.mapi_demo.activities.attended;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.text.InputType;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.OnApplyWindowInsetsListener;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.widget.NestedScrollView;

import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.gson.Gson;

import org.apache.commons.lang.StringUtils;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Currency;
import java.util.List;
import java.util.stream.Collectors;

import demo.mapi.ccv.eu.mapi_demo.AndroidLogger;
import demo.mapi.ccv.eu.mapi_demo.DelegationFactory;
import demo.mapi.ccv.eu.mapi_demo.Flow;
import demo.mapi.ccv.eu.mapi_demo.FlowData;
import demo.mapi.ccv.eu.mapi_demo.MapiDemoState;
import demo.mapi.ccv.eu.mapi_demo.R;
import demo.mapi.ccv.eu.mapi_demo.formatters.OutputFormatter;
import demo.mapi.ccv.eu.mapi_demo.handlers.OpiChFlowHandler;
import demo.mapi.ccv.eu.mapi_demo.handlers.OpiDeFlowHandler;
import demo.mapi.ccv.eu.mapi_demo.handlers.OpiNlFlowHandler;
import demo.mapi.ccv.eu.mapi_demo.handlers.PaymentHandler;
import eu.ccvlab.mapi.core.Callback;
import eu.ccvlab.mapi.core.CashierInputCallback;
import eu.ccvlab.mapi.core.DeliveryBoxCallback;
import eu.ccvlab.mapi.core.InputCallback;
import eu.ccvlab.mapi.core.RequestType;
import eu.ccvlab.mapi.core.api.PasswordLevel;
import eu.ccvlab.mapi.core.api.request.CardDetectionRequest;
import eu.ccvlab.mapi.core.api.request.Flexo;
import eu.ccvlab.mapi.core.api.request.OnlineAgentRequest;
import eu.ccvlab.mapi.core.api.request.Param;
import eu.ccvlab.mapi.core.api.request.TerminalAdministrationOperationType;
import eu.ccvlab.mapi.core.api.request.TerminalCommandRequest;
import eu.ccvlab.mapi.core.api.request.TerminalOperationType;
import eu.ccvlab.mapi.core.api.request.TokenPurpose;
import eu.ccvlab.mapi.core.api.response.delegate.PaymentDelegate;
import eu.ccvlab.mapi.core.api.response.delegate.TerminalDelegate;
import eu.ccvlab.mapi.core.api.response.delegate.TokenDelegate;
import eu.ccvlab.mapi.core.api.response.result.ConfigData;
import eu.ccvlab.mapi.core.api.response.result.Error;
import eu.ccvlab.mapi.core.api.response.result.TokenResult;
import eu.ccvlab.mapi.core.logging.MPALogging;
import eu.ccvlab.mapi.core.machine.CustomerSignatureCallback;
import eu.ccvlab.mapi.core.machine.InputCommandCallback;
import eu.ccvlab.mapi.core.payment.Agent;
import eu.ccvlab.mapi.core.payment.CardReadDelegate;
import eu.ccvlab.mapi.core.payment.CardReadResult;
import eu.ccvlab.mapi.core.payment.CardReaderResult;
import eu.ccvlab.mapi.core.payment.CardReaderStatusDelegate;
import eu.ccvlab.mapi.core.payment.CashierInput;
import eu.ccvlab.mapi.core.payment.DisplayTextRequest;
import eu.ccvlab.mapi.core.payment.EReceiptRequest;
import eu.ccvlab.mapi.core.payment.MainTextRequest;
import eu.ccvlab.mapi.core.payment.Money;
import eu.ccvlab.mapi.core.payment.Payment;
import eu.ccvlab.mapi.core.payment.PaymentAdministrationResult;
import eu.ccvlab.mapi.core.payment.PaymentReceipt;
import eu.ccvlab.mapi.core.payment.PaymentResult;
import eu.ccvlab.mapi.core.payment.TextLine;
import eu.ccvlab.mapi.core.payment.receipt.AdditionalReceiptTextRequest;
import eu.ccvlab.mapi.core.payment.receipt.ReceiptDeviceTarget;
import eu.ccvlab.mapi.core.requests.ResultState;
import eu.ccvlab.mapi.core.terminal.ExternalTerminal;
import eu.ccvlab.mapi.core.terminal.LanguageCode;

public class AttendedTerminalActivity extends AppCompatActivity implements DelegationFactory {
    private static final String TAG = "AttendedActivity";
    private static final String OPI_DE = "OPI-DE";
    private static final String OPI_NL = "OPI-NL";
    private static final String OPI_CH = "OPI-CH";
    //App state management
    private MapiDemoState appState = new MapiDemoState();
    private Flow activeFlow;
    private OutputFormatter resultFieldFormatter;
    private boolean deliveryBoxGoodsDelivered = true;
    private String previousPaymentId;
    private boolean isSingleSocketSelected;
    private String opiDialect;
    private String hostname;
    private int port;
    private int compatibilityPort;
    private String receiptNumber;
    private Integer referenceNumber;
    private String transactionId;
    private String approvalCode;
    private String token;
    private RequestType originalTransactionRequestType;
    private String selectedOpiFlow = "Payment";
    private BottomSheetDialog dialog;
    private Thread askMerchantInputThread;
    private String hashData;
    private String myCCVShopLocationId;

    public TextView resultTextField;
    public Spinner currencySpinner;
    public EditText amount;
    public LinearLayout abortButtonLayout;
    public LinearLayout opiFlowLayout;
    public Button startButton;
    private AndroidLogger androidLogger = new AndroidLogger("AttendedTerminalActivity");


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.attended_terminal);
        WindowCompat.setDecorFitsSystemWindows(this.getWindow(), false);
        applyEdgeToEdgeOnRV(findViewById(R.id.attended_activity_layout));
        resultTextField = findViewById(R.id.output);
        currencySpinner = findViewById(R.id.currency);
        amount = findViewById(R.id.amount);
        abortButtonLayout = findViewById(R.id.abort_button_layout);
        opiFlowLayout = findViewById(R.id.opi_flow_layout);
        startButton = findViewById(R.id.start_button);
        findViewById(R.id.abort_on_new_connection_button).setOnClickListener((View view) -> abortOnNewConnectionButtonClicked());
        findViewById(R.id.abort_button).setOnClickListener((View view) -> actionButtonClicked());
        findViewById(R.id.start_button).setOnClickListener((View view) -> onStartClicked());
        findViewById(R.id.opi_flow_options_button).setOnClickListener((View view) -> buttons());
        MPALogging.addLogger(androidLogger);
    }

    @Override
    protected void onDestroy() {
        MPALogging.removeLogger(androidLogger);
        super.onDestroy();
    }

    public void applyEdgeToEdgeOnRV(View rootView) {
        ViewCompat.setOnApplyWindowInsetsListener(rootView, new OnApplyWindowInsetsListener() {
            @NonNull
            @Override
            public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat windowInsets) {
                Insets bars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
                // Apply insets
                v.setPadding(
                        bars.left,
                        bars.top,
                        bars.right,
                        bars.bottom
                );
                return windowInsets;
            }
        });
    }

    @Override
    protected void onStart() {
        super.onStart();
        loadLastAppState();
        initWidgets();
    }

    public void abortOnNewConnectionButtonClicked() {
        startFlow(Flow.ABORT_ON_NEW_CONNECTION);
    }

    public void actionButtonClicked() {
        startFlow(Flow.ABORT);
    }

    private void hideKeyboard() {
        View view = getCurrentFocus();
        if (view != null) {
            InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
        }
    }

    private void initWidgets() {
        resultTextField.setMovementMethod(new ScrollingMovementMethod());
        currencySpinner = initCurrencyButton();

        OutputFormatter.OutputAppender outputAppender = new OutputFormatter.OutputAppender() {
            @Override
            public void append(String line) {
                runOnUiThread(() -> {
                    resultTextField.append(line);
                });
            }
        };
        resultFieldFormatter = new OutputFormatter(outputAppender);

        refreshDisplayedSettings();
    }

    public void onStartClicked() {
        hideKeyboard();
        Flow newFlow = Flow.get(selectedOpiFlow);
        //If there is a flow on-going the actionButton acts like an abort otherwise a payment will be executed
        if (activeFlow == null) {
            resultTextField.setText("");
            saveNewAppState();
            startFlow(newFlow);
            abortButtonLayout.setVisibility(View.VISIBLE);
            opiFlowLayout.setVisibility(View.GONE);
            startAndroid10Intent();
        }
    }

    private void startAndroid10Intent() {
        if (android.os.Build.VERSION.SDK_INT >= 29) {
            Intent sendIntent = new Intent();
            sendIntent.setAction("eu.ccv.payment.action.SHOW_PAYMENT");
            if (sendIntent.resolveActivity(AttendedTerminalActivity.this.getPackageManager()) != null) {
                sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(sendIntent);
            } else {
                Toast.makeText(AttendedTerminalActivity.this, "couldn't find launcher intent for payment", Toast.LENGTH_SHORT).show();
            }
        }
    }

    private void startFlow(Flow flow) {
        Log.i(TAG, "Handling flow: " + flow);
        resultTextField.setText("");
        activeFlow = flow;
        PaymentHandler flowHandler = flowHandler();
        ViewBasedFlowData flowData = new ViewBasedFlowData();
        switch (flow) {
            case PAYMENT:
                Payment payment = buildPayment(flowData);
                this.previousPaymentId = payment.requestId();
                saveNewAppState();
                flowHandler.startPayment(externalTerminal().shiftNumber(1), payment);
                break;
            case DISPENSER_DELIVERED_GOODS:
                deliveryBoxGoodsDelivered = true;
                startFlow(Flow.PAYMENT);
                break;
            case DISPENSER_NOT_DELIVERED_GOODS:
                deliveryBoxGoodsDelivered = false;
                startFlow(Flow.PAYMENT);
                break;
            case REFUND:
                flowHandler.startRefund(externalTerminal(), Payment.Type.REFUND, flowData.getAmount(), flowData.getReferenceNumber());
                break;
            case VOID:
                Money money = flowData.hasAmount() ? flowData.getAmount() : null;
                flowHandler.startVoid(externalTerminal(), Payment.Type.VOID, money, flowData.getTransactionId(), null, null);
                break;
            case VOID_ANY:
                performVoid(flowHandler, flowData, null);
                break;
            case CARD_NOT_PRESENT_VOID_ANY:
                performVoid(flowHandler, flowData, token);
                break;
            case EXTERNAL_VOID:
                performExternalVoid(flowHandler, flowData);
                break;
            case ABORT:
                flowHandler.startAbort(externalTerminal());
                break;
            case SILENT_ABORT:
                flowHandler.silentAbort(externalTerminal());
                break;
            case ABORT_ON_NEW_CONNECTION:
                flowHandler.startAbortOnNewConnection(externalTerminal());
                break;
            case REPEAT_LAST_PAYMENT:
                flowHandler.startRepeatLastMessage(externalTerminal());
                break;
            case REPEAT_LAST_SERVICE_MESSAGE:
                flowHandler.startRepeatLastServiceMessage(externalTerminal());
                break;
            case RETRIEVE_LAST_TICKET:
                flowHandler.startRetrieveLastTicket(externalTerminal());
                break;
            case STATUS:
                flowHandler.startStatus(externalTerminal());
                break;
            case PERFORM_PERIOD_CLOSING:
                flowHandler.performPeriodClosing(externalTerminal());
                break;
            case TRANSACTION_OVERVIEW:
                flowHandler.getTransactionOverview(externalTerminal().shiftNumber(1));
                break;
            case INITIALISATION:
                flowHandler.startInitialisation(externalTerminal());
                break;
            case CARD_READER_STATUS:
                flowHandler.startCardReaderStatus(externalTerminal());
                break;
            case CARD_READ:
                flowHandler.startCardRead(externalTerminal(), flowData.getAmount());
                break;
            case CARD_READ_SECUREID:
                flowHandler.startCardReadSecureID(externalTerminal(), flowData.getAmount());
                break;
            case PAYMENT_AFTER_CARD_READ:
                flowHandler.startPaymentAfterCardRead(externalTerminal(), Payment.Type.SALE, flowData.getAmount());
                break;
            case SHOW_MENU:
                flowHandler.showTerminalMenu(externalTerminal());
                break;
            case CARD_RESERVATION:
                flowHandler.startCardReservation(externalTerminal(), Payment.Type.RESERVATION, flowData.getAmount());
                break;
            case SALES_AFTER_RESERVATION:
                flowHandler.startSaleAfterReservation(externalTerminal(), Payment.Type.SALE, flowData.getAmount(), approvalCode, null);
                break;
            case CARD_NOT_PRESENT_SALES_AFTER_RESERVATION:
                flowHandler.startSaleAfterReservation(externalTerminal(), Payment.Type.SALE, flowData.getAmount(), approvalCode, token);
                break;
            case RESERVATION_ADJUSTMENT:
                flowHandler.startReservationAdjustment(externalTerminal(), Payment.Type.RESERVATION, flowData.getAmount(), approvalCode);
                break;
            case RESERVATION_ADJUSTMENT_NO_CARD_PRESENT:
                flowHandler.startReservationAdjustmentNoCardPresent(externalTerminal(), Payment.Type.RESERVATION, flowData.getAmount(), approvalCode, token);
                break;
            case CALLTMS:
                flowHandler.callTMS(externalTerminal(), "");
                break;
            case FACTORY_RESET:
                flowHandler.startFactoryReset(externalTerminal());
                break;
            case RESET_TO_FACTORY_SETTINGS:
                flowHandler.startResetToFactorySettings(externalTerminal());
                break;
            case SERVICE_MENU:
                flowHandler.startServiceMenu(externalTerminal());
                break;
            case OAM_SERVER_APPLICATIONS:
                flowHandler.oamServerApplications(externalTerminal());
                break;
            case DIAGNOSIS:
                flowHandler.startDiagnosis(externalTerminal(), TerminalOperationType.DIAGNOSIS);
                break;
            case EMV_DIAGNOSIS:
                flowHandler.startDiagnosis(externalTerminal(), TerminalOperationType.EMV_DIAGNOSIS);
                break;
            case CONFIGURATION_DIAGNOSIS:
                flowHandler.startDiagnosis(externalTerminal(), TerminalOperationType.CONFIGURATION_DIAGNOSIS);
                break;
            case CONFIG_DATA:
                flowHandler.startConfigDataRetrieval(externalTerminal(), TerminalAdministrationOperationType.CONFIG_DATA);
                break;
            case RECOVER_PAYMENT:
                flowHandler.recoverPayment(externalTerminal(), previousPaymentId);
                break;
            case RECOVER_PAYMENT_CUT_PREVIOUS_CONNECTION:
                flowHandler.recoverPayment(externalTerminal().cutPreviousConnection(true), previousPaymentId);
                break;
            case CARD_CIRCUITS:
                flowHandler.cardCircuits(externalTerminal());
                break;
            case AUTHORISATION_BY_VOICE:
                showAuthorisationByVoiceDialog(flowHandler, flowData);
                break;
            case READ_UID:
                flowHandler.startReadUID(externalTerminal());
                break;
            case FLEXO:
                flowHandler.flexo(externalTerminal());
                break;
            case TAXFREE:
                flowHandler.terminalCommand(externalTerminal(), TerminalCommandRequest.builder().totalAmount(flowData.getAmount()).reducedTaxAmount(Money.EUR(5.55)).build(), Agent.TAXFREE);
                break;
            case MOBILE_PHONE_PREPAID:
                flowHandler.terminalCommand(externalTerminal(), TerminalCommandRequest.builder().totalAmount(flowData.getAmount()).prepaidCard("41").serialNumber("ABC1234567890").build(), Agent.MOBILE_PHONE_PREPAID);
                break;
            case TICKET_REPRINT_PERIOD_CLOSING:
                flowHandler.ticketReprintPeriodClosing(externalTerminal());
                break;
            case CHECK_PASSWORD:
                flowHandler.checkPassword(externalTerminal().passwordLevel(PasswordLevel.MANAGER));
                break;
            case ELME_VERSION_INFO:
                flowHandler.elmeVersionInfo(externalTerminal());
                break;
            case PREAUTHORIZATION:
                flowHandler.startPayment(externalTerminal(), buildAuthorizeAndCapturePayment(flowData, Payment.Type.PREAUTHORIZATION));
                break;
            case EXTENDED_PREAUTHORIZATION:
                performAuthoriseOrCapture(flowHandler, flowData, Payment.Type.EXTENDED_PREAUTHORIZATION);
                break;
            case FINANCIAL_ADVICE:
                performAuthoriseOrCapture(flowHandler, flowData, Payment.Type.FINANCIAL_ADVICE);
                break;
            case EXTENDED_FINANCIAL_ADVICE:
                performAuthoriseOrCapture(flowHandler, flowData, Payment.Type.EXTENDED_FINANCIAL_ADVICE);
                break;
            case GIFT_CARD_BALANCE:
                flowHandler.giftCardBalance(externalTerminal());
                break;
            case GIFT_CARD_ACTIVATION:
                flowHandler.giftCardActivation(externalTerminal(), flowData.getAmount());
                break;
            case ACTIVATE_TERMINAL:
                flowHandler.activateTerminal(externalTerminal());
                break;
            case STARTUP:
                flowHandler.startup(externalTerminal());
                break;
            case TOKEN:
                flowHandler.token(externalTerminal());
                break;
            case REFUND_WITH_TOKEN:
                flowHandler.startRefundWithToken(externalTerminal(), Payment.Type.REFUND, flowData.getAmount(), hashData);
                break;
            case CARD_DETECTION:
                flowHandler.cardDetection(externalTerminal(), CardDetectionRequest.builder().amount(flowData.getAmount()).includeFlexo(true).tokenPurpose(TokenPurpose.TRACKING).build());
                break;
            case ONLINE_AGENT_FLEXO:
                flowHandler.onlineAgent(externalTerminal(), OnlineAgentRequest.builder().flexoRequest(createDefaultFlexoRequest()).build());
                break;
            case LOGIN:
                flowHandler.login(externalTerminal());
                break;
            default:
                log("Method not supported");
        }
    }

    private Flexo createDefaultFlexoRequest() {
        return Flexo.builder()
                .serviceName("Mifare")
                .scriptName("DISPLAY_AND_BEEP")
                .param(List.of(
                        Param.builder()
                                .name("line1")
                                .value("MiFare Contactless")
                                .build(),
                        Param.builder()
                                .name("line2")
                                .value("Transactie akkoord")
                                .build(),
                        Param.builder()
                                .name("beep")
                                .value("SuccessBeep")
                                .build()))
                .build();
    }

    private void performVoid(PaymentHandler flowHandler, ViewBasedFlowData flowData, String token) {
        askVoidInput(dialogInterface -> {
            if (approvalCode != null && originalTransactionRequestType != null) {
                Money money = flowData.hasAmount() ? flowData.getAmount() : null;
                flowHandler.startVoid(externalTerminal(), Payment.Type.VOID, money, approvalCode, originalTransactionRequestType, token);
            } else {
                finishFlow();
            }
        });
    }

    private void performExternalVoid(PaymentHandler flowHandler, ViewBasedFlowData flowData) {
        askStanAndApprovalCode(new DialogInterface.OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialogInterface) {
                if (canPerformExternalVoid()) {
                    flowHandler.startExternalVoid(externalTerminal(), buildExternalVoidPayment(flowData));
                } else {
                    finishFlow();
                }
            }
        });
    }

    private void performAuthoriseOrCapture(PaymentHandler flowHandler, ViewBasedFlowData flowData, Payment.Type extendedPreauthorization) {
        askStanAndApprovalCode(new DialogInterface.OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialogInterface) {
                if (canPerformAuthorisationOrCapture()) {
                    flowHandler.startPayment(externalTerminal(), buildAuthorizeAndCapturePayment(flowData, extendedPreauthorization));
                } else {
                    finishAuthorizationOrCapture();
                }
            }
        });
    }

    private void askStanAndApprovalCode(DialogInterface.OnDismissListener onDismissListener) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("Transaction STAN");
        builder.setMessage("Please enter the STAN number of the transaction you want to authorize, capture or void");

        final EditText input = new EditText(this);
        input.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED);
        builder.setView(input);

        builder.setPositiveButton("OK", (dialogInterface, i) -> {
            if (StringUtils.isNotEmpty(input.getText().toString())) {
                this.transactionId = input.getText().toString();
                dialog.dismiss();
                askApprovalCode(onDismissListener);
            } else {
                showEmptyInputMessage();
            }
        });

        builder.setNegativeButton("Cancel", (dialogInterface, i) -> {
            dialog.dismiss();
            finishFlow();
        });
        runOnUiThread(builder::show);
    }

    private void askApprovalCode(DialogInterface.OnDismissListener onDismissListener) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setOnDismissListener(onDismissListener);
        builder.setTitle("Transaction approvalCode");
        builder.setMessage("Please enter the approvalCode of the transaction you want to authorize, capture or void");

        final EditText input = new EditText(this);
        input.setInputType(InputType.TYPE_CLASS_TEXT);
        builder.setView(input);

        builder.setPositiveButton("OK", (dialogInterface, i) -> {
            if (StringUtils.isNotEmpty(input.getText().toString())) {
                this.approvalCode = input.getText().toString();
                if (Flow.EXTERNAL_VOID.equals(activeFlow) || Flow.VOID.equals(activeFlow)) {
                    askOriginalTransactionType(onDismissListener);
                } else {
                    dialog.dismiss();
                }
            } else {
                showEmptyInputMessage();
            }
        });

        builder.setNegativeButton("Cancel", (dialogInterface, i) -> {
            dialog.dismiss();
            finishFlow();
        });
        runOnUiThread(builder::show);
    }

    private void askVoidInput(DialogInterface.OnDismissListener onDismissListener) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setOnDismissListener(onDismissListener);
        builder.setTitle("Original Transaction info");
        builder.setMessage("Please enter the approvalCode and original transaction type of the transaction you want to void(cancel)");

        final LinearLayout ll = new LinearLayout(this);
        ll.setPadding(20, 10, 20, 10);
        ll.setOrientation(LinearLayout.VERTICAL);

        final EditText input = new EditText(this);
        input.setInputType(InputType.TYPE_CLASS_TEXT);
        input.setHint("approvalCode");
        ll.addView(input);

        final ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1, RequestType.values());
        Spinner spinner = new Spinner(this);
        spinner.setAdapter(adapter);

        ll.addView(spinner);
        builder.setView(ll);

        builder.setPositiveButton("OK", (dialogInterface, i) -> {
            if (StringUtils.isNotEmpty(input.getText().toString())) {
                this.approvalCode = input.getText().toString();
                this.originalTransactionRequestType = RequestType.findByValue(spinner.getSelectedItem().toString()).get();
                dialog.dismiss();
            } else {
                showEmptyInputMessage();
            }
        });

        builder.setNegativeButton("Cancel", (dialogInterface, i) -> {
            dialog.dismiss();
            finishFlow();
        });
        runOnUiThread(builder::show);
    }

    private void askOriginalTransactionType(DialogInterface.OnDismissListener onDismissListener) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setOnDismissListener(onDismissListener);
        builder.setTitle("Transaction Type of the original transaction");
        builder.setMessage("Please enter the transaction type of the transaction you want to void");


        final ArrayAdapter<CharSequence> adapter = createSpinnerAdapter(this, R.array.request_types);
        Spinner spinner = new Spinner(this);
        spinner.setAdapter(adapter);

        builder.setView(spinner);

        builder.setPositiveButton("OK", (dialogInterface, i) -> {
            if (StringUtils.isNotEmpty(spinner.getSelectedItem().toString())) {
                this.originalTransactionRequestType = RequestType.findByValue(spinner.getSelectedItem().toString()).get();
                if (Flow.EXTERNAL_VOID.equals(activeFlow)) {
                    askOriginalTransactionType(onDismissListener);
                } else {
                    dialog.dismiss();
                }
            } else {
                showEmptyInputMessage();
            }
        });

        builder.setNegativeButton("Cancel", (dialogInterface, i) -> {
            dialog.dismiss();
            finishFlow();
        });
        runOnUiThread(builder::show);
    }

    private void finishAuthorizationOrCapture() {
        finishFlow();
        Toast.makeText(this, "No previous transaction data available", Toast.LENGTH_LONG).show();
    }

    private boolean canPerformAuthorisationOrCapture() {
        return transactionId != null && approvalCode != null;
    }

    private boolean canPerformExternalVoid() {
        return transactionId != null && approvalCode != null && originalTransactionRequestType != null;
    }

    private PaymentHandler flowHandler() {
        switch (opiDialect) {
            case OPI_DE:
                return new OpiDeFlowHandler(this);
            case OPI_CH:
                return new OpiChFlowHandler(this);
            case OPI_NL:
            default:
                return new OpiNlFlowHandler(this, appState);
        }
    }

    private void showAuthorisationByVoiceDialog(PaymentHandler flowHandler, ViewBasedFlowData flowData) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("VoiceReferralAID");
        final EditText input = new EditText(this);
        input.setInputType(InputType.TYPE_CLASS_TEXT);
        builder.setView(input);
        builder.setPositiveButton("OK", (dialogInterface, i) -> {
            if (StringUtils.isNotEmpty(input.getText().toString())) {
                flowHandler.authorisationByVoice(externalTerminal(), Payment.Type.AUTHORISATION_BY_VOICE, flowData.getAmount(), input.getText().toString());
            } else {
                dialog.cancel();
                finishFlow();
            }
        });

        builder.setNegativeButton("Cancel", (dialogInterface, i) -> {
            dialog.cancel();
            finishFlow();
        });
        builder.show();
    }

    private Payment buildPayment(ViewBasedFlowData flowData) {
        return Payment
                .builder()
                .type(Payment.Type.SALE)
                .amount(flowData.getAmount())
                .lastReceiptNumber(flowData.getReceiptNumber())
                .myCCVShopLocationId(myCCVShopLocationId)
                .referenceNumber(flowData.getReferenceNumber())
                .build();
    }

    private Payment buildAuthorizeAndCapturePayment(ViewBasedFlowData flowData, Payment.Type type) {
        return Payment
                .builder()
                .type(type)
                .amount(flowData.getAmount())
                .transactionId(transactionId)
                .approvalCode(approvalCode)
                .build();
    }

    private Payment buildExternalVoidPayment(ViewBasedFlowData flowData) {
        return Payment
                .builder()
                .type(Payment.Type.VOID)
                .amount(flowData.getAmount())
                .transactionId(transactionId)
                .approvalCode(approvalCode)
                .originalTransactionRequestType(originalTransactionRequestType)
                .myCCVShopLocationId(myCCVShopLocationId)
                .build();
    }


    private List<AdditionalReceiptTextRequest> additionalReceiptTextRequest() {
        List<String> additionalReceiptText = new ArrayList<>();

        additionalReceiptText.add("Welcome To My shop");
        additionalReceiptText.add("Thank You For Coming");

        List<AdditionalReceiptTextRequest> requestList = new ArrayList<>();
        requestList.add(new AdditionalReceiptTextRequest(ReceiptDeviceTarget.PRINTER, additionalReceiptText));

        return requestList;
    }

    private ExternalTerminal externalTerminal() {
        ExternalTerminal.TerminalType type = terminalType();
        if (!isSingleSocketSelected) {
            return ExternalTerminal.builder()
                    .ipAddress(hostname)
                    .port(port)
                    .compatibilityPort(compatibilityPort)
                    .socketMode(ExternalTerminal.SocketMode.DUAL_SOCKET)
                    .terminalType(type)
                    .workstationId("MAPI WORKSTATION")
                    .languageCode(LanguageCode.EN)
                    .requestToken(true)
                    .build();
        } else {
            return ExternalTerminal.builder()
                    .ipAddress(hostname)
                    .port(port)
                    .socketMode(ExternalTerminal.SocketMode.SINGLE_SOCKET)
                    .terminalType(type)
                    .workstationId("MAPI WORKSTATION")
                    .languageCode(LanguageCode.EN)
                    .requestToken(true)
                    .build();
        }
    }

    private ExternalTerminal.TerminalType terminalType() {
        switch (opiDialect) {
            case OPI_DE:
                return ExternalTerminal.TerminalType.OPI_DE;
            case OPI_CH:
                return ExternalTerminal.TerminalType.OPI_CH;
            case OPI_NL:
            default:
                return ExternalTerminal.TerminalType.OPI_NL;
        }
    }

    @Override
    public TokenDelegate createTokenDelegate(String context) {
        return new TokenDelegate() {
            @Override
            public void cardUID(String cardUID) {
                resultFieldFormatter.print("CardUUID: " + cardUID);
            }

            @Override
            public void onTokenSuccess(TokenResult tokenResult) {
                resultFieldFormatter.print(tokenResult.toString());
                finishFlow();
            }

            @Override
            public void onError(Error error) {
                log("[TokenService] OnTokenError " + error.toString());
                finishFlow();
            }
        };
    }

    @Override
    public CardReadDelegate createCardReadDelegate(String context) {
        return new CardReadDelegate() {
            @Override
            public void onFailure(ResultState resultState) {
                log("Card Read failure: " + resultState);
                resultFieldFormatter.printResultState(resultState);
                finishFlow();
            }

            @Override
            public void onSuccess(CardReadResult cardReadResult) {
                log("Success: " + cardReadResult);
                resultFieldFormatter.print(cardReadResult.toString());
                finishFlow();
            }

            @Override
            public void onError(Error error) {
                log("Card Read Error: " + error);
                resultFieldFormatter.printError(error.mapiError());
                finishFlow();
            }

            @Override
            public void showTerminalOutputLines(List<TextLine> lines) {
                log("showTerminalOutputLines: " + lines);
            }
        };
    }

    /**
     * Create payment delegate. Does some basic interaction and logging.
     *
     * @param context Used for logging messages, to clarify the context to the reader in which a message was logged.
     */
    @NonNull
    @Override
    public PaymentDelegate createPaymentDelegate(String context) {
        return new PaymentDelegate() {
            @Override
            public void showTerminalOutputLines(List<TextLine> lines) {
                for (TextLine line : lines) {
                    log(String.format("[ %s ]: Terminal output: %s", context, line));
                }
                resultFieldFormatter.printTicketLines(lines);
            }

            @Override
            public void printMerchantReceiptAndSignature(PaymentReceipt receipt) {
                log("[ " + context + " ] : Print merchant receipt and signature");
                resultFieldFormatter.printTicket(receipt.plainTextLines());
            }

            @Override
            public void eReceipt(EReceiptRequest eReceiptRequest) {
                log("[ " + context + " ] : Print eReceipt");
                log(String.format(eReceiptRequest.toString()));
            }

            @Override
            public void printCustomerReceiptAndSignature(PaymentReceipt receipt) {
                log("[ " + context + " ] : Print customer receipt and signature");
                resultFieldFormatter.printTicket(receipt.plainTextLines());
            }

            @Override
            public void printDccOffer(PaymentReceipt receipt) {
                log("[ " + context + " ] : Print DCC Offer");
                resultFieldFormatter.printTicket(receipt.plainTextLines());
            }

            @Override
            public void onPaymentSuccess(PaymentResult paymentResult) {
                log("[ " + context + " ]: success");
                resultFieldFormatter.printResult(paymentResult);

                if (StringUtils.isNotEmpty(paymentResult.token())) {
                    token = paymentResult.token();
                }

                if (StringUtils.isNotEmpty(paymentResult.hashData())) {
                    hashData = paymentResult.hashData();
                }

                if (StringUtils.isNotEmpty(paymentResult.approvalCode())) {
                    approvalCode = paymentResult.approvalCode();
                }
                finishFlow();
            }

            @Override
            public void onError(Error error) {
                log("[ " + context + " ]: " + error.mapiError().description());
                if (error.result() != null) {
                    resultFieldFormatter.print("ErrorCode: " + error.result().errorCode());
                }
                resultFieldFormatter.printResult(error.result());
                finishFlow();

                if (error.result() != null && error.result().resultState().equals(ResultState.PRINT_LAST_TICKET)) {
                    showReprintTicketDialog();
                }
            }

            @Override
            public void drawCustomerSignature(CustomerSignatureCallback callback) {
                log("[ " + context + " ]: Draw customer signature");
                callback.signature(new byte[100]);
            }

            @Override
            public void askCustomerSignature(PaymentDelegate.SignatureAsked signatureAsked) {
                log("[ " + context + " ]: Ask Customer Signature");
                showSignatureIdentificationDialog("Ask Customer Signature");
                signatureAsked.signatureAsked();
            }

            @SuppressWarnings("PMD.SystemPrintln")
            @Override
            public void inputCommand(List<String> list, String s, int i, InputCommandCallback inputCommandCallback) {
                System.out.println(s);
            }

            @Override
            public void askCashierInput(CashierInput cashierInput, InputCallback inputCallback) {
                CashierInputCallback cashierInputCallback;
                switch (cashierInput.commandRequest().command()) {
                    case "GetChar":
                        cashierInputCallback = CashierInputCallback.builder().charCommand("test merchant reference").build();
                        break;
                    case "GetDecimals":
                        cashierInputCallback = CashierInputCallback.builder().numberCommand(25).build();
                        break;
                    case "GetConfirmation":
                        cashierInputCallback = CashierInputCallback.builder().booleanCommand(true).build();
                        break;
                    default:
                        throw new IllegalStateException("Unexpected value: " + cashierInput.commandRequest().command());
                }
                inputCallback.passInput(cashierInputCallback);
            }

            private OutputFormatter printer = new OutputFormatter(new OutputFormatter.OutputAppender() {
                @Override
                public void append(String line) {
                    resultTextField.append(line);
                }
            });

            @Override
            public void printJournalReceipt(PaymentReceipt var1) {
                log("[ " + context + " ]: Print journal receipt ");
                resultFieldFormatter.printTicket(var1.plainTextLines());
            }

            @Override
            public void storeEJournal(String journal) {
                log("[ " + context + " ]: Store E-journal " + journal);
            }

            @Override
            public void askCustomerIdentification(Callback callback) {
                log("[ " + context + " ]: Ask customer identification");
                showSignatureIdentificationDialog("Ask Customer Identification");
                callback.proceed();
            }

            @Override
            public void askCustomerSignature(Callback callback) {
                log("[ " + context + " ]: Ask customer signature");
                showSignatureIdentificationDialog("Ask Customer Signature");
                callback.proceed();
            }

            @Override
            public void askMerchantSignature(Callback callback) {
                log("[ " + context + " ]: Ask merchant signature");
                showSignatureIdentificationDialog("Ask Merchant Signature");
                callback.proceed();
            }

            @SuppressWarnings("PMD.SystemPrintln")
            @Override
            public void showOnCustomerDisplay(MainTextRequest mainText, DisplayTextRequest subText) {
                System.out.println("customerDisplay");
                if (mainText != null) {
                    log(String.format("[ %s ]: Customer Display MainText: %s", context, mainText.text()));
                }

                if (subText != null) {
                    log(String.format("[ %s ]: Customer Display SubText: %s", context, subText.text()));
                }
            }

            @Override
            public void onDeliverGoodsOrServices(DeliveryBoxCallback deliveryBoxCallback) {
                deliveryBoxCallback.proceed(deliveryBoxGoodsDelivered);
            }
        };
    }

    private void showReprintTicketDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        ;
        //Setting message manually and performing action on button click
        builder.setMessage("Reprint Last Ticket").setTitle("Reprint Last Ticket")
                .setCancelable(false)
                .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        startFlow(Flow.RETRIEVE_LAST_TICKET);
                        dialog.cancel();
                    }
                })
                .setNegativeButton("No", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        //  Action for 'NO' Button
                        dialog.cancel();
                    }
                });
        //Creating dialog box
        AlertDialog alert = builder.create();
        //Setting the title manually
        alert.setTitle("Reprint Last Tickets");
        alert.show();
    }

    private void showSignatureIdentificationDialog(String message) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        ;
        //Setting message manually and performing action on button click
        builder.setMessage(message).setTitle(message)
                .setCancelable(false)
                .setPositiveButton("Ok", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.cancel();
                    }
                });
        //Creating dialog box
        AlertDialog alert = builder.create();
        //Setting the title manually
        alert.setTitle("Signature and/or Identification");
        alert.show();
    }


    @Override
    public CardReaderStatusDelegate createCardReaderStatusDelegate(final String context) {
        return new CardReaderStatusDelegate() {
            @Override
            public void onError(Error error) {
                log("[ " + context + " ]: error: " + error);
                finishFlow();
            }

            @Override
            public void onSuccess(CardReaderResult cardReaderResult) {
                log("[ " + context + " ]: success: " + cardReaderResult);
                finishFlow();
            }

        };
    }

    @NonNull
    @Override
    public TerminalDelegate createPaymentAdministrationDelegate(final String context) {
        return new TerminalDelegate() {
            @Override
            public void printJournalReceipt(PaymentReceipt var1) {
                resultFieldFormatter.print("[ " + context + " ]: Journal receipt");
                resultFieldFormatter.printTicket(var1.plainTextLines());
            }

            @Override
            public void storeEJournal(String journal) {
                resultFieldFormatter.print("[ " + context + " ]: Store e-journal" + journal);
            }

            @Override
            public void showTerminalOutputLines(List<TextLine> lines) {
                for (TextLine line : lines) {
                    log(String.format("[ %s ]: Terminal output: %s", context, line));
                }
                resultFieldFormatter.printTicketLines(lines);
            }

            @Override
            public void configData(ConfigData configData) {
                resultFieldFormatter.print("[ " + context + " ]: config data: " + configData);
            }

            @Override
            public void onPaymentAdministrationSuccess(PaymentAdministrationResult result) {
                resultFieldFormatter.print("[ " + context + " ]: success");
                resultFieldFormatter.printResult(result);
                result.tunnelResponse();
                finishFlow();
            }

            @Override
            public void onError(Error error) {
                resultFieldFormatter.print("[ " + context + " ]: error: " + error.mapiError().description());
                resultFieldFormatter.printResult(error.result());
                finishFlow();
            }

            @Override
            public void printMerchantReceiptAndSignature(PaymentReceipt receipt) {
                log("[ " + context + " ]: Print Merchant Receipt");
                resultFieldFormatter.printTicket(receipt.plainTextLines());
            }

            @Override
            public void printCustomerReceiptAndSignature(PaymentReceipt receipt) {
                log("[ " + context + " ]: Print Customer Receipt");
                if (receipt.getLogo() != null) {
                    resultFieldFormatter.print(receipt.getLogo().getBitmap());
                    resultFieldFormatter.print(receipt.getLogo().getName());
                }

                if (receipt.getBarcode() != null) {
                    resultFieldFormatter.print(receipt.getBarcode().type());
                    resultFieldFormatter.print(receipt.getBarcode().nr());
                    resultFieldFormatter.print(receipt.getBarcode().bitmap());
                }

                resultFieldFormatter.printTicket(receipt.plainTextLines());
            }

            @Override
            public void askCashierInput(CashierInput cashierInput, InputCallback inputCallback) {
                askInput(cashierInput, inputCallback);
            }


            @Override
            public void inputCommand(List<String> lines, String command, int length, InputCommandCallback inputCommandCallback) {
                askInputDE(lines, command, length, inputCommandCallback);
            }

            @Override
            public void eReceipt(EReceiptRequest eReceiptRequest) {
                resultFieldFormatter.print("[ " + context + " ] : Print eReceipt");
                resultFieldFormatter.print(String.format(eReceiptRequest.toString()));
            }

            @Override
            public void cardUID(String cardUID) {
                resultFieldFormatter.print("CardUUID: " + cardUID);
            }


        };
    }

    private void askInput(CashierInput cashierInput, InputCallback inputCallback) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);

        int inputType = getInputType(cashierInput);

        if (cashierInput.commandRequest().command().equals("GetMenu")) {
            builder.setTitle(cashierInput.outputLines().remove(0).getText());
            builder.setMessage(cashierInput.outputLines().stream().map(textLine -> textLine.getMenuItem() + " " + textLine.getText() + "\n").collect(Collectors.joining()));
        } else {
            builder.setTitle("Get Input");
            builder.setMessage(cashierInput.output());
        }

        final EditText input = new EditText(this);
        if (inputType != 0) {
            input.setInputType(inputType);
            builder.setView(input);
        }

        builder.setPositiveButton("OK", (dialogInterface, i) -> {
            askInputPositiveButton(cashierInput, inputCallback, input);
        });

        builder.setNegativeButton("Cancel", (dialogInterface, i) -> {
            askInputNegativeButton(cashierInput, inputCallback);
        });
        runOnUiThread(builder::show);
    }

    private int getInputType(CashierInput cashierInput) {
        int inputType = 0;
        switch (cashierInput.commandRequest().command()) {
            case "GetMenu":
            case "GetDecimals":
            case "GetAmount":
                inputType = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED;
                break;
            case "GetConfirmation":
                break;
            case "GetChar":
            default:
                inputType = InputType.TYPE_CLASS_TEXT;
                break;
        }

        return inputType;
    }

    private void askInputDE(List<String> lines, String command, int length, InputCommandCallback inputCommandCallback) {

        int inputType = 0;

        switch (command) {
            case "GetMenu":
            case "GetDecimals":
                inputType = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED;
                break;
            case "GetConfirmation":
                break;
            case "GetChar":
            default:
                inputType = InputType.TYPE_CLASS_TEXT;
                break;
        }

        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("User Input");
        final EditText input = new EditText(this);
        if (inputType != 0) {
            input.setInputType(inputType);
            builder.setView(input);
        }
        builder.setMessage(lines.toString());
        builder.setPositiveButton("OK", (dialogInterface, i) -> {
            askInputDEPositiveButton(command, inputCommandCallback, input.getText().toString());
        });

        builder.setNegativeButton("Cancel", (dialogInterface, i) -> {
            askInputDENegativeButton(command, inputCommandCallback);
        });
        runOnUiThread(builder::show);
    }

    private void askInputDEPositiveButton(String command, InputCommandCallback inputCommandCallback, String inputText) {
        if (command.equals("GetConfirmation")) {
            new Thread(() -> inputCommandCallback.input("Y")).start();
        } else if (StringUtils.isNotEmpty(inputText)) {
            new Thread(() -> inputCommandCallback.input(inputText)).start();
        } else {
            dialog.cancel();
            finishFlow();
        }
    }

    private void askInputDENegativeButton(String command, InputCommandCallback inputCommandCallback) {
        if (command.equals("GetConfirmation")) {
            new Thread(() -> inputCommandCallback.input("N")).start();
        } else {
            dialog.cancel();
            finishFlow();
        }
    }

    private void askInputNegativeButton(CashierInput cashierInput, InputCallback inputCallback) {
        switch (cashierInput.commandRequest().command()) {
            case "GetMenu":
            case "GetChar":
                askMerchantInputThread = new Thread(() -> inputCallback.abort());
                dialog.dismiss();
                break;
            case "GetConfirmation":
                askMerchantInputThread = new Thread(() -> inputCallback.passInput(CashierInputCallback.builder().booleanCommand(false).build()));
                dialog.dismiss();
                break;
            default:
                dialog.dismiss();
                finishFlow();
                break;
        }

        startAskMerchantInputThread();
    }

    private void askInputPositiveButton(CashierInput cashierInput, InputCallback inputCallback, EditText input) {
        switch (cashierInput.commandRequest().command()) {
            case "GetMenu":
            case "GetDecimals":
                if (StringUtils.isNotEmpty(input.getText().toString())) {
                    askMerchantInputThread = new Thread(() -> inputCallback.passInput(CashierInputCallback.builder().numberCommand(Integer.valueOf(input.getText().toString())).build()));
                    dialog.dismiss();
                } else {
                    showEmptyInputMessage();
                }
                break;
            case "GetAmount":
                if (StringUtils.isNotEmpty(input.getText().toString())) {
                    askMerchantInputThread = new Thread(() -> inputCallback.passInput(CashierInputCallback.builder().numberCommand(new BigDecimal("2.50").setScale(2).unscaledValue().intValue()).build()));
                    dialog.dismiss();
                } else {
                    showEmptyInputMessage();
                }
                break;
            case "GetConfirmation":
                askMerchantInputThread = new Thread(() -> inputCallback.passInput(CashierInputCallback.builder().booleanCommand(true).build()));
                dialog.dismiss();
                break;
            default:
                if (StringUtils.isNotEmpty(input.getText().toString())) {
                    askMerchantInputThread = new Thread(() -> inputCallback.passInput(CashierInputCallback.builder().charCommand(input.getText().toString()).build()));
                    dialog.dismiss();
                } else {
                    showEmptyInputMessage();
                }
                break;
        }

        startAskMerchantInputThread();
    }

    private void startAskMerchantInputThread() {
        try {
            if (askMerchantInputThread != null) {
                askMerchantInputThread.start();
            }
        } finally {
            if (askMerchantInputThread != null && askMerchantInputThread.isAlive()) {
                askMerchantInputThread.interrupt();
            }
        }
    }

    private void showEmptyInputMessage() {
        Toast.makeText(this, "Input is empty", Toast.LENGTH_LONG);
    }

    private void finishFlow() {
        activeFlow = null;
        abortButtonLayout.setVisibility(View.GONE);
        opiFlowLayout.setVisibility(View.VISIBLE);
    }

    private void log(String line) {
        if (resultTextField != null)
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    resultTextField.append(String.format("%s\n", line));
                }
            });

    }

    private Spinner initCurrencyButton() {
        final ArrayAdapter<CharSequence> adapter = createSpinnerAdapter(this, R.array.currencies);
        currencySpinner.setAdapter(adapter);
        return currencySpinner;
    }

    @NonNull
    private ArrayAdapter<CharSequence> createSpinnerAdapter(AttendedTerminalActivity context, int flows_array) {
        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(context,
                flows_array, android.R.layout.simple_spinner_item);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        return adapter;
    }

    public void buttons() {
        NestedScrollView view = (NestedScrollView) getLayoutInflater().inflate(R.layout.lo_bottomsheets_content, null);
        LinearLayout linearLayout = view.findViewById(R.id.lnrlyot_bottomsheets);
        int opi_flows = opiFlow();
        String[] arrayList = getResources().getStringArray(opi_flows);

        for (String flow : arrayList) {
            Button button = new Button(this);
            button.setText(flow);
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    selectedOpiFlow = ((Button) view).getText().toString();
                    startButton.setText(selectedOpiFlow);
                    dialog.dismiss();
                }
            });
            linearLayout.addView(button);
        }

        dialog = new BottomSheetDialog(this);
        dialog.setContentView(view);
        dialog.show();
    }

    private int opiFlow() {
        switch (opiDialect) {
            case OPI_DE:
                return R.array.flows_array_opi_de;
            case OPI_CH:
                return R.array.flows_array_opi_ch;
            case OPI_NL:
            default:
                return R.array.flows_array_opi_nl;
        }
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        Intent intent;

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            intent = new Intent(this, AttendedSettingsActivity.class);
            startActivity(intent);
            return true;
        } else if (id == R.id.action_clear_output) {
            resultTextField.setText("");
            return true;
        } else if (id == R.id.action_certification_test_url) {
            intent = new Intent(this, AttendedCertificationTestsActivity.class);
            startActivity(intent);
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    class ViewBasedFlowData implements FlowData {

        @Override
        public Money getAmount() {
            return new Money(new BigDecimal(amount.getText().toString()), Currency.getInstance(currencySpinner.getSelectedItem().toString()));
        }

        @Override
        public boolean hasAmount() {
            return amount.getText().toString().trim().length() > 0;
        }

        @Override
        public int getPort() {
            return port;
        }

        @Override
        public int getCompatibilityPort() {
            return compatibilityPort;
        }

        @Override
        public String getReceiptNumber() {
            return receiptNumber;
        }

        @Override
        public Integer getReferenceNumber() {
            return referenceNumber;
        }

        @Override
        public String getTransactionId() {
            if (transactionId != null && !transactionId.isEmpty()) {
                return transactionId;
            } else {
                return null;
            }
        }

    }

    @SuppressLint("LongLogTag")
    private void saveNewAppState() {
        try {
            String protocol = appState.selectedOpiProtocol();
            appState.setPreviousPaymentId(protocol, previousPaymentId);
            appState.setAmount(protocol, amount.getText().toString());
            appState.setCurrency(protocol, currencySpinner.getSelectedItem().toString());
            Gson gson = new Gson();
            String json = gson.toJson(this.appState);
            FileOutputStream fileOutputStream = getApplicationContext().openFileOutput("state.json", Context.MODE_PRIVATE);
            fileOutputStream.write(json.getBytes());
            fileOutputStream.close();
        } catch (IOException e) {
            Log.e("AttendedTerminalActivity", "Failed to save app state: " + e.getLocalizedMessage());
        }
    }

    @SuppressLint("LongLogTag")
    private void loadLastAppState() {
        String xml = null;
        try {
            File file = getApplicationContext().getFileStreamPath("state.json");

            int size = (int) file.length();
            byte[] bytes = new byte[size];
            BufferedInputStream buf;

            buf = new BufferedInputStream(new FileInputStream(file));
            buf.read(bytes, 0, bytes.length);
            buf.close();

            xml = new String(bytes);
        } catch (IOException e) {
            Log.e("AttendedTerminalActivity", "Unable to read previous app state");
        }

        this.appState = xml != null ? new Gson().fromJson(xml, MapiDemoState.class) : new MapiDemoState();
    }

    private void refreshDisplayedSettings() {
        previousPaymentId = appState.getPreviousPaymentId(appState.selectedOpiProtocol());
        isSingleSocketSelected = appState.isSingleSocket(appState.selectedOpiProtocol());
        opiDialect = appState.selectedOpiProtocol();
        hostname = appState.getHostname(appState.selectedOpiProtocol());
        port = Integer.parseInt(appState.getPort(appState.selectedOpiProtocol()));
        compatibilityPort = Integer.parseInt(appState.getCompatibilityPort(appState.selectedOpiProtocol()));
        receiptNumber = appState.getReceiptNumber(appState.selectedOpiProtocol());
        referenceNumber = appState.getReferenceNumber(appState.selectedOpiProtocol());
        transactionId = appState.getTransactionId(appState.selectedOpiProtocol());
        currencySpinner.setSelection(((ArrayAdapter<CharSequence>) currencySpinner.getAdapter()).getPosition(appState.getCurrency(appState.selectedOpiProtocol())));
        amount.setText(appState.getAmount(appState.selectedOpiProtocol()));
        myCCVShopLocationId = appState.getMyCCVShopLocationId(appState.selectedOpiProtocol());
    }

}
