package com.greenaddress.greenbits.ui;

import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.nfc.NdefMessage;
import android.nfc.NfcAdapter;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.design.widget.Snackbar;
import android.text.Editable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.StrikethroughSpan;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.MultiAutoCompleteTextView;
import android.widget.TextView;

import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.blockstream.libwally.Wally;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.greenaddress.greenapi.CryptoHelper;
import com.greenaddress.greenapi.LoginData;

import de.schildbach.wallet.ui.ScanActivity;


public class MnemonicActivity extends LoginActivity implements View.OnClickListener {

    private static final String TAG = MnemonicActivity.class.getSimpleName();

    private static final int PINSAVE = 1337;
    private static final int QRSCANNER = 1338;
    private static final int CAMERA_PERMISSION = 150;

    private MultiAutoCompleteTextView mMnemonicText;
    private CircularButton mOkButton;
    private TextView mScanButton;

    final private MultiAutoCompleteTextView.Tokenizer mTokenizer = new MultiAutoCompleteTextView.Tokenizer() {
        private boolean isspace(final CharSequence t, final int pos) {
            return Character.isWhitespace(t.charAt(pos));
        }

        public int findTokenStart(final CharSequence t, int cursor) {
            final int end = cursor;
            while (cursor > 0 && !isspace(t, cursor - 1))
                --cursor;
            while (cursor < end && isspace(t, cursor))
                ++cursor;
            return cursor;
        }

        public int findTokenEnd(final CharSequence t, int cursor) {
            final int end = t.length();
            while (cursor < end && !isspace(t, cursor))
                ++cursor;
            return cursor;
        }

        public CharSequence terminateToken(final CharSequence t) {
            int cursor = t.length();

            while (cursor > 0 && isspace(t, cursor - 1))
                cursor--;
            if (cursor > 0 && isspace(t, cursor - 1))
                return t;
            if (t instanceof Spanned) {
                final SpannableString sp = new SpannableString(t + " ");
                TextUtils.copySpansFrom((Spanned) t, 0, t.length(), Object.class, sp, 0);
                return sp;
            }
            return t + " ";
        }
    };

    @Override
    protected void onCreateWithService(final Bundle savedInstanceState) {
        Log.i(TAG, getIntent().getType() + ' ' + getIntent());

        setTitleWithNetwork(R.string.title_activity_mnemonic);
        mMnemonicText = UI.find(this, R.id.mnemonicText);
        mOkButton = UI.find(this,R.id.mnemonicOkButton);
        mScanButton = UI.find(this,R.id.mnemonicScanIcon);

        mOkButton.setOnClickListener(this);

        final boolean haveCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
        UI.showIf(haveCamera, mScanButton);
        if (haveCamera)
            mScanButton.setOnClickListener(this);

        final ArrayAdapter<String> adapter;
        adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, MnemonicHelper.mWordsArray);
        mMnemonicText.setAdapter(adapter);
        mMnemonicText.setThreshold(1);
        mMnemonicText.setTokenizer(mTokenizer);
        mMnemonicText.addTextChangedListener(new UI.TextWatcher() {
            @Override
            public void onTextChanged(final CharSequence t, final int start,
                                      final int before, final int count) {
                markInvalidWords();
            }
        });

        mMnemonicText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(final TextView textView, final int actionId, final KeyEvent keyEvent) {
                if (actionId == EditorInfo.IME_ACTION_GO || keyEvent.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
                    doLogin();
                    return true;
                }
                return false;
            }
        });

        NFCIntentMnemonicLogin();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        UI.unmapClick(mOkButton);
        UI.unmapClick(mScanButton);
    }

    private void markInvalidWords() {
        final Editable e = mMnemonicText.getText();
        for (final StrikethroughSpan s : e.getSpans(0, e.length(), StrikethroughSpan.class))
            e.removeSpan(s);

        int start = 0;
        for (final String word : e.toString().split(" ")) {
            final int end = start + word.length();
            if (!MnemonicHelper.mWords.contains(word))
                e.setSpan(new StrikethroughSpan(), start, end, 0);
            start = end + 1;
        }
    }

    private void promptToFixInvalidWord(final String badWord, final int start, final int end) {
        // FIXME: Show a list of closest words instead of just one
        final String closeWord = MnemonicHelper.getClosestWord(MnemonicHelper.mWordsArray, badWord);
        final Snackbar snackbar = Snackbar
            .make(mMnemonicText, getString(R.string.invalidWord, badWord, closeWord), Snackbar.LENGTH_LONG)
            .setAction("Correct", new View.OnClickListener() {
                @Override
                public void onClick(final View v) {
                    // FIXME: Use start/end to replace and try logging in again
                    setMnemonic(getMnemonic().replace(badWord, closeWord));
                }
            });
        final View v = snackbar.getView();
        v.setBackgroundColor(Color.DKGRAY);
        final TextView textView = UI.find(v, android.support.design.R.id.snackbar_text);
        textView.setTextColor(Color.WHITE);
        snackbar.show();
    }

    protected int getMainViewId() { return R.layout.activity_mnemonic; }

    private String getMnemonic() {
        return UI.getText(mMnemonicText).replaceAll("\\s+", " ").trim();
    }

    private void setMnemonic(final String mnemonic) {
        if (!UI.getText(mMnemonicText).equals(mnemonic))
            mMnemonicText.setText(mnemonic);
        mMnemonicText.setSelection(mnemonic.length(), mnemonic.length());
    }

    private void enableLogin() {
        mOkButton.stopLoading();
        mMnemonicText.setEnabled(true);
    }

    private void doLogin() {
        final String mnemonic = getMnemonic();
        setMnemonic(mnemonic); // Trim mnemonic when OK pressed

        if (mOkButton.isLoading())
            return;

        if (mService.isLoggedIn()) {
            toast(R.string.err_mnemonic_activity_logout_required);
            return;
        }

        if (!mService.isConnected()) {
            toast(R.string.err_send_not_connected_will_resume);
            return;
        }

        final String words[] = mnemonic.split(" ");
        if (words.length != 24 && words.length != 27) {
            toast(R.string.err_mnemonic_activity_invalid_mnemonic);
            return;
        }

        int start = 0;
        for (final String word : words) {
            final int end = start + word.length();
            if (!MnemonicHelper.mWords.contains(word)) {
                promptToFixInvalidWord(word, start, end);
                return;
            }
            start = end + 1;
        }

        try {
            Wally.bip39_mnemonic_validate(Wally.bip39_get_wordlist("en"), mnemonic);
        } catch (final IllegalArgumentException e) {
            toast(R.string.err_mnemonic_activity_invalid_mnemonic); // FIXME: Use different error message
            return;
        }

        mOkButton.startLoading();;
        mMnemonicText.setEnabled(false);
        hideKeyboardFrom(mMnemonicText);

        final AsyncFunction<Void, LoginData> connectToLogin = new AsyncFunction<Void, LoginData>() {
            @Override
            public ListenableFuture<LoginData> apply(final Void input) {
                if (words.length != 27)
                    return mService.login(mnemonic);

                // Encrypted mnemonic
                return Futures.transformAsync(askForPassphrase(), new AsyncFunction<String, LoginData>() {
                    @Override
                    public ListenableFuture<LoginData> apply(final String passphrase) {
                        return mService.login(CryptoHelper.decrypt_mnemonic(mnemonic, passphrase));
                    }
                });
            }
        };

        final ListenableFuture<LoginData> loginFuture;
        loginFuture = Futures.transformAsync(mService.onConnected, connectToLogin, mService.getExecutor());

        Futures.addCallback(loginFuture, new FutureCallback<LoginData>() {
            @Override
            public void onSuccess(final LoginData result) {
                if (getCallingActivity() == null) {
                    final Intent savePin = PinSaveActivity.createIntent(MnemonicActivity.this, mService.getMnemonic());
                    startActivityForResult(savePin, PINSAVE);
                } else {
                    setResult(RESULT_OK);
                    finishOnUiThread();
                }
            }

            @Override
            public void onFailure(final Throwable t) {
                final boolean accountDoesntExist = t instanceof ClassCastException;
                final String message = accountDoesntExist ? "Account doesn't exist" : "Login failed";
                t.printStackTrace();
                MnemonicActivity.this.runOnUiThread(new Runnable() {
                    public void run() {
                        MnemonicActivity.this.toast(message);
                        enableLogin();
                    }
                });
            }
        }, mService.getExecutor());
    }

    private ListenableFuture<String> askForPassphrase() {
        final SettableFuture<String> fn = SettableFuture.create();
        runOnUiThread(new Runnable() {
            public void run() {
                final View v = UI.inflateDialog(MnemonicActivity.this, R.layout.dialog_passphrase);
                final EditText passphraseValue = UI.find(v, R.id.passphraseValue);
                passphraseValue.requestFocus();
                final MaterialDialog dialog = UI.popup(MnemonicActivity.this, "Encryption passphrase")
                        .customView(v, true)
                        .onPositive(new MaterialDialog.SingleButtonCallback() {
                            @Override
                            public void onClick(final MaterialDialog dlg, final DialogAction which) {
                                fn.set(UI.getText(passphraseValue));
                            }
                        })
                        .onNegative(new MaterialDialog.SingleButtonCallback() {
                            @Override
                            public void onClick(final MaterialDialog dlg, final DialogAction which) {
                                enableLogin();
                            }
                        }).build();
                UI.showDialog(dialog);
            }
        });
        return fn;
    }

    @Override
    public void onClick(final View v) {
        if (v == mOkButton)
            doLogin();
        else if (v == mScanButton)
            onScanClicked();
    }

    private void onScanClicked() {
        final String[] perms = { "android.permission.CAMERA" };
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1 &&
            checkSelfPermission(perms[0]) != PackageManager.PERMISSION_GRANTED)
            requestPermissions(perms, CAMERA_PERMISSION);
        else {
            final Intent scanner = new Intent(MnemonicActivity.this, ScanActivity.class);
            startActivityForResult(scanner, QRSCANNER);
        }
    }

    private void loginOnUiThread() {
        if (mService.onConnected == null || getMnemonic().equals(mService.getMnemonic()))
            return;

        CB.after(mService.onConnected, new CB.Op<Void>() {
            @Override
            public void onSuccess(final Void result) {
                runOnUiThread(new Runnable() { public void run() { doLogin(); } });
            }
        });
    }

    private static byte[] getNFCPayload(final Intent intent) {
        final Parcelable[] extra = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
        return ((NdefMessage) extra[0]).getRecords()[0].getPayload();
    }

     private void NFCIntentMnemonicLogin() {
        final Intent intent = getIntent();

        if (intent == null || !NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction()))
            return;

        mMnemonicText.setTextColor(Color.WHITE);

        if (intent.getType().equals("x-gait/mnc")) {
            // Unencrypted NFC
            mMnemonicText.setText(CryptoHelper.mnemonic_from_bytes(getNFCPayload(intent)));
            loginOnUiThread();

        } else if (intent.getType().equals("x-ga/en"))
            // Encrypted NFC
            CB.after(askForPassphrase(), new CB.Op<String>() {
                @Override
                public void onSuccess(final String passphrase) {
                    mMnemonicText.setText(CryptoHelper.decrypt_mnemonic(getNFCPayload(intent),
                                                                        passphrase));
                    loginOnUiThread();
                }
            });
    }

    @Override
    protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case PINSAVE:
                onLoginSuccess();
                break;
            case QRSCANNER:
                if (data != null && data.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT) != null) {
                    mMnemonicText.setText(data.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT));
                    doLogin();
                }
                break;
        }
    }

    @Override
    public boolean onCreateOptionsMenu(final Menu menu) {
        // FIXME: Show connectivity status to user
        // Inflate the menu; this adds items to the action bar if it is present.
        // getMenuInflater().inflate(R.menu.mnemonic, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
        return item.getItemId() == R.id.action_settings || super.onOptionsItemSelected(item);
    }

    @Override
    public void onRequestPermissionsResult(final int requestCode, final String[] permissions, final int[] granted) {
        if (requestCode == CAMERA_PERMISSION &&
            isPermissionGranted(granted, R.string.err_qrscan_requires_camera_permissions))
            startActivityForResult(new Intent(this, ScanActivity.class), QRSCANNER);
    }
}