package com.shukria.softpos.ui;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.*;

import androidx.activity.ComponentActivity;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;

import com.mypinpad.android.hapticslibrary.CardScheme;
import com.mypinpad.android.hapticslibrary.SchemeHapticsAnimationView;
import com.mypinpad.tsdk.api.Terminal;
import com.mypinpad.tsdk.api.TerminalSdk;
import com.mypinpad.tsdk.api.callbacks.SessionActivationFailureCause;
import com.mypinpad.tsdk.api.callbacks.UiMessage;
import com.mypinpad.tsdk.api.models.TransactionType;
import com.shukria.softpos.*;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Executors;

public class MainActivity extends ComponentActivity {

    private final String LOG_TAG = "Transaction";
    private final SimpleDateFormat LOG_DATE_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);

    private MainViewModel viewModel;
    private ArrayAdapter<String> logAdapter;

    private Button createTerminalButton;
    private Button activateButton;
    private Button purchaseTransactionWithPinButton;
    private Button purchaseTransactionButton;
    private Button refundTransactionButton;
    private Button deactivateButton;
    private Button disposeTerminalButton;
    private Button clearLogsButton;
    private Button shareLogsButton;
    private List<Button> terminalButtons;
    private TextView uiMessageText;
    private TextView sessionValidityText;
    private ListView logListView;
    private SchemeHapticsAnimationView schemeHapticsAnimationView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Route to softPOS UI unless debug mode is enabled in settings
        if (!new AppSettings(this).isDebugMode()) {
            startActivity(new Intent(this, SoftPosActivity.class));
            finish();
            return;
        }

        viewModel = new ViewModelProvider(this).get(MainViewModel.class);

        if (Utils.deviceNfcStatus(this) != Utils.DeviceNfcStatus.NFC_ENABLED) {
            startActivity(new Intent(this, NfcErrorActivity.class));
            finish();
            return;
        }

        setContentView(R.layout.main_activity);

        initUI();
        addLog(getTsdkInfo());
    }

    private String getTsdkInfo() {
        return "Versions:\n"
                + "Terminal SDK: " + TerminalSdk.getVersion() + "\n"
                + "EMV Library:" + TerminalSdk.getEmvLibVersion();
    }

    @Override
    protected void onStart() {
        super.onStart();

        viewModel.getLogger().sink = this::addLog;
        viewModel.getViewData().observe(this, this::updateUi);
    }

    @Override
    protected void onStop() {
        super.onStop();
        viewModel.getLogger().sink = ExampleApp.TsdkLogger.DEFAULT_SINK;
        viewModel.getViewData().removeObservers(this);
    }

    private void updateUi(ViewData viewData) {
        addLog("ViewData received:" + viewData.getClass().getSimpleName());
        // update ui message
        String message = getUiMessageTextOrNull(viewData);
        if (message == null) {
            uiMessageText.setVisibility(View.GONE);
        } else {
            uiMessageText.setText(message);
            uiMessageText.setVisibility(View.VISIBLE);
            addLog("UiMessage: " + message);
        }

        // Session validity text will be visible only when we have a session
        sessionValidityText.setVisibility(View.GONE);

        if (viewData instanceof ViewData.TerminalErrorViewData) {
            addLog("Unrecoverable Error: " + ((ViewData.TerminalErrorViewData) viewData).logMessage);
            enableExclusively(terminalButtons, createTerminalButton);

            return;
        }

        if (viewData instanceof ViewData.SessionErrorViewData) {
            addLog("Error: " + ((ViewData.SessionErrorViewData) viewData).cause);
            if (((ViewData.SessionErrorViewData) viewData).cause == SessionActivationFailureCause.DEACTIVATED) {
                // The terminal is disposed due to some errors.
                // We should dispose of the terminal and create a new one.
                viewModel.disposeTerminal(true);
            } else {
                enableExclusively(terminalButtons, activateButton, disposeTerminalButton);
            }

            return;
        }

        if (viewData instanceof ViewData.TerminalNotCreatedViewData) {
            enableExclusively(terminalButtons, createTerminalButton);

            return;
        }

        if (viewData instanceof ViewData.TerminalInitialisingViewData) {
            viewModel.initialise(new MainViewModel.TerminalInitialiseListener() {
                @Override
                public void onTerminalInitialised(Terminal terminal) {
                    runOnUiThread(() -> {
                        updateUi(new ViewData.TerminalReadyViewData());
                    });
                }

                @Override
                public void onTerminalInitialisedFailed(Throwable throwable) {
                    runOnUiThread(() -> {
                        viewModel.disposeTerminal(false);
                        updateUi(new ViewData.TerminalErrorViewData(throwable));
                    });
                }
            });
            disableAll(terminalButtons);

            return;
        }

        if (viewData instanceof ViewData.TerminalReadyViewData) {
            enableExclusively(terminalButtons, activateButton, disposeTerminalButton);

            return;
        }

        if (viewData instanceof ViewData.TerminalActivatingViewData) {
            disableAll(terminalButtons);

            return;
        }

        if (viewData instanceof ViewData.TerminalSessionTimeoutViewData) {
            Toast.makeText(this, "Session timed out, reactivating session", Toast.LENGTH_SHORT).show();
            viewModel.reactivate((ViewData.TerminalSessionTimeoutViewData) viewData);
            return;
        }

        if (viewData instanceof ViewData.TerminalInSessionViewData) {
            ViewData.TerminalInSessionViewData specificViewData = (ViewData.TerminalInSessionViewData) viewData;
            sessionValidityText.setVisibility(View.VISIBLE);
            String expiryTime = SimpleDateFormat.getDateTimeInstance()
                    .format(specificViewData.session.getValidUntil());
            sessionValidityText.setText("Session is valid until: " + expiryTime);

            purchaseTransactionButton.setOnClickListener(
                    (v) -> viewModel.startTransaction(specificViewData, TransactionType.PURCHASE, 0L));
            purchaseTransactionWithPinButton
                    .setOnClickListener((v) -> viewModel.startTransactionWithPin(specificViewData));
            refundTransactionButton.setOnClickListener(
                    (v) -> viewModel.startTransaction(specificViewData, TransactionType.REFUND, 0L));
            deactivateButton.setOnClickListener((v) -> viewModel.deactivate(specificViewData));
            enableExclusively(terminalButtons,
                    purchaseTransactionButton,
                    purchaseTransactionWithPinButton,
                    refundTransactionButton,
                    deactivateButton,
                    disposeTerminalButton);

            return;
        }

        if (viewData instanceof ViewData.TerminalInProcessingViewData) {
            deactivateButton.setOnClickListener(
                    (v) -> viewModel.deactivate(((ViewData.TerminalInProcessingViewData) viewData)));
            enableExclusively(terminalButtons,
                    deactivateButton,
                    disposeTerminalButton);

            return;
        }

        if (viewData instanceof ViewData.DisplaySchemeHapticsViewData) {
            playSchemeHaptics(((ViewData.DisplaySchemeHapticsViewData) viewData).cardScheme);

            return;
        }

        if (viewData instanceof ViewData.ProcessingFinishedViewData) {
            addLog(((ViewData.ProcessingFinishedViewData) viewData).logMessage);
            disableAll(terminalButtons);

            return;
        }

        throw new RuntimeException("Unknown view data type: " + viewData.getClass().getSimpleName());
    }

    private void disableAll(List<Button> buttons) {
        enableExclusively(buttons);
    }

    private void enableExclusively(List<Button> buttons, Button... buttonsToEnable) {
        List<Button> buttonsToEnableList = Arrays.asList(buttonsToEnable);

        buttons.forEach(
                (button) -> {
                    boolean enabled = buttonsToEnableList.contains(button);
                    button.setEnabled(enabled);
                    button.setVisibility(enabled ? View.VISIBLE : View.GONE);
                });
    }

    private void initUI() {
        // Find views
        createTerminalButton = findViewById(R.id.createTerminalButton);
        activateButton = findViewById(R.id.activateButton);
        deactivateButton = findViewById(R.id.deactivateButton);
        disposeTerminalButton = findViewById(R.id.disposeTerminalButton);
        purchaseTransactionButton = findViewById(R.id.purchaseTransactionButton);
        purchaseTransactionWithPinButton = findViewById(R.id.purchaseTransactionWithPinButton);
        refundTransactionButton = findViewById(R.id.refundTransactionButton);
        terminalButtons = Arrays.asList(
                createTerminalButton,
                activateButton,
                purchaseTransactionButton,
                purchaseTransactionWithPinButton,
                refundTransactionButton,
                deactivateButton,
                disposeTerminalButton);
        uiMessageText = findViewById(R.id.uiMessageText);
        sessionValidityText = findViewById(R.id.session_info);
        clearLogsButton = findViewById(R.id.clearLogsButton);
        shareLogsButton = findViewById(R.id.shareLogsButton);
        logListView = findViewById(R.id.logListView);

        createTerminalButton.setOnClickListener((v) -> viewModel.createTerminal());
        activateButton.setOnClickListener((v) -> viewModel.activate());
        disposeTerminalButton.setOnClickListener((v) -> viewModel.disposeTerminal(true));

        // Set up Log
        clearLogsButton.setOnClickListener(v -> logAdapter.clear());

        shareLogsButton.setOnClickListener(view -> Executors.newCachedThreadPool().submit(() -> {
            try {
                File logFile = Utils.writeToInternalFile(this, writer -> {
                    for (int i = 0; i < logAdapter.getCount(); i++) {
                        try {
                            writer.write(logAdapter.getItem(i) + "\n");
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });

                Log.d(LOG_TAG, "Logs written to " + logFile);

                Intent sendIntent = new Intent();
                sendIntent.setAction(Intent.ACTION_SEND);
                sendIntent.putExtra(Intent.EXTRA_TEXT, readText(logFile, 1_000_000));
                sendIntent.setType("text/plain");
                startActivity(Intent.createChooser(sendIntent, null));
            } catch (Exception e) {
                e.printStackTrace();
                runOnUiThread(() -> Toast.makeText(this, "Error sending log file: " + e, Toast.LENGTH_SHORT).show());
            } catch (Error e) {
                runOnUiThread(() -> {
                    throw e;
                });
            }
        }));

        logAdapter = new ArrayAdapter<String>(this, R.layout.simple_list_item) {
            @Override
            public boolean isEnabled(int position) {
                return false;
            }
        };
        logListView.setAdapter(logAdapter);

        schemeHapticsAnimationView = findViewById(R.id.transactionProcessedView);
    }

    private Void addLog(String message) {
        // We need the millisecond part of the current time also, only Calendar can
        // provide that
        Calendar cal = Calendar.getInstance();
        String time = LOG_DATE_FORMAT.format(cal.getTime());
        String msecs = String.format(Locale.getDefault(), "%03d", cal.get((Calendar.MILLISECOND)));
        Log.d(LOG_TAG, message);

        // We might be running on some other thread
        runOnUiThread(() -> {

            logAdapter.add(String.format("%s.%s: %s", time, msecs, message));

            // Scroll to the bottom
            logListView.setSelection(logAdapter.getCount() - 1);
        });

        return null;
    }

    private void playSchemeHaptics(CardScheme cardScheme) {
        schemeHapticsAnimationView.setVisibility(View.VISIBLE);
        schemeHapticsAnimationView.play(cardScheme, null, null, (v) -> {
            v.setVisibility(View.GONE);
            viewModel.schemeHapticsFinished();
            return null;
        });
    }

    private String getUiMessageTextOrNull(ViewData viewData) {
        UiMessage uiMessage = null;

        if (viewData instanceof ViewData.TerminalInProcessingViewData) {
            uiMessage = ((ViewData.TerminalInProcessingViewData) viewData).uiMessage;
        }
        if (viewData instanceof ViewData.ProcessingFinishedViewData) {
            uiMessage = ((ViewData.ProcessingFinishedViewData) viewData).uiMessage;
        }

        return uiMessage != null ? uiMessage.getId().name() : null;
    }

    private String readText(File file, int n) throws IOException {
        StringBuilder text = new StringBuilder();
        BufferedReader reader = new BufferedReader(new FileReader(file));
        try {
            String line;
            while ((line = reader.readLine()) != null && line.length() < n) {
                text.append(line).append("\n");
            }
        } finally {
            reader.close();
        }
        return text.toString();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.action_settings) {
            startActivity(new Intent(this, SettingsActivity.class));
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}
